Skip to content

Commit

Permalink
update test
Browse files Browse the repository at this point in the history
  • Loading branch information
ciaozhang committed Jan 26, 2023
1 parent 026e685 commit 4cf75e3
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1264,11 +1264,11 @@ private async Task<TokenValidationResult> ValidateTokenAsync(JsonWebToken jsonWe
validationParameters.ValidateWithLKG = true;
var recoverableException = tokenValidationResult.Exception;

foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfiguraitonFromCache().Keys)
foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfiguraitonFromCache())
{
if (!lkgConfiguration.Equals(currentConfiguration) && TokenUtilities.IsRecoverableConfiguration(jsonWebToken.Kid, currentConfiguration, lkgConfiguration, recoverableException))
{
tokenValidationResult = await ValidateTokenAsync(jsonWebToken, validationParameters, currentConfiguration).ConfigureAwait(false);
tokenValidationResult = await ValidateTokenAsync(jsonWebToken, validationParameters, lkgConfiguration).ConfigureAwait(false);

if (tokenValidationResult.IsValid)
return tokenValidationResult;
Expand Down
16 changes: 12 additions & 4 deletions src/Microsoft.IdentityModel.Tokens/BaseConfigurationComparer.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Microsoft.IdentityModel.Tokens
{
/// <summary>
/// Comparison class for a <see cref="BaseConfiguration"/>.
/// </summary>
internal class BaseConfigurationComparer : IEqualityComparer<BaseConfiguration>
{
public bool Equals(BaseConfiguration config1, BaseConfiguration config2)
Expand All @@ -26,7 +26,15 @@ public bool Equals(BaseConfiguration config1, BaseConfiguration config2)

public int GetHashCode(BaseConfiguration config)
{
return (config.Issuer + string.Join("", config.SigningKeys.Select(x => x.InternalId).OrderBy(x => x.FirstOrDefault()))).GetHashCode();
int defaultHash = string.Empty.GetHashCode();
int hashCode = defaultHash;
hashCode ^= string.IsNullOrEmpty(config.Issuer) ? defaultHash : config.Issuer.GetHashCode();
foreach(string internalId in config.SigningKeys.Select(x => x.InternalId))
{
hashCode ^= internalId.GetHashCode();
}

return hashCode;
}
}
}
44 changes: 23 additions & 21 deletions src/Microsoft.IdentityModel.Tokens/BaseConfigurationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,25 @@ public virtual Task<BaseConfiguration> GetBaseConfigurationAsync(CancellationTok
throw new NotImplementedException();
}

/// <summary>
/// Gets all valid last known good configurations from the cache.
/// </summary>
/// <returns>A collection of all valid last known good configurations.</returns>
internal ICollection<BaseConfiguration> GetValidLkgConfiguraitonFromCache()
{
if (_lkgConfigurationCache == null)
return null;

var expiredLkgConfiguration = _lkgConfigurationCache.Where(x => x.Value < DateTime.UtcNow).ToArray();

foreach (KeyValuePair<BaseConfiguration, DateTime> lkgConfiguration in expiredLkgConfiguration)
{
_lkgConfigurationCache.Remove(lkgConfiguration.Key);
}

return _lkgConfigurationCache.Any() ? _lkgConfigurationCache.Keys : null;
}

/// <summary>
/// The last known good configuration or LKG (a configuration retrieved in the past that we were able to successfully validate a token against).
/// </summary>
Expand All @@ -100,30 +119,13 @@ public BaseConfiguration LastKnownGoodConfiguration
_lkgConfigurationCache[_lastKnownGoodConfiguration] = DateTime.UtcNow + LastKnownGoodLifetime;

//remove expired configuration to avoid memory leak
foreach (KeyValuePair<BaseConfiguration, DateTime> lkgConfiguration in _lkgConfigurationCache)
{
if (lkgConfiguration.Value < DateTime.UtcNow)
_lkgConfigurationCache.Remove(lkgConfiguration.Key);
}
}
}
var expiredLkgConfiguration = _lkgConfigurationCache.Where(x => x.Value < DateTime.UtcNow).ToArray();

/// <summary>
///
/// </summary>
/// <returns></returns>
public IDictionary<BaseConfiguration, DateTime> GetValidLkgConfiguraitonFromCache()
{
if (_lkgConfigurationCache == null)
return null;

foreach (KeyValuePair<BaseConfiguration, DateTime> lkgConfiguration in _lkgConfigurationCache)
{
if (lkgConfiguration.Value < DateTime.UtcNow)
foreach (KeyValuePair<BaseConfiguration, DateTime> lkgConfiguration in expiredLkgConfiguration)
{
_lkgConfigurationCache.Remove(lkgConfiguration.Key);
}
}

return _lkgConfigurationCache;
}

/// <summary>
Expand Down
34 changes: 20 additions & 14 deletions src/Microsoft.IdentityModel.Tokens/TokenUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,30 +199,36 @@ internal static bool IsRecoverableException(Exception exception)
/// <summary>
/// Check whether the given configuration is recoverable by LKG.
/// </summary>
/// <param name="kid"></param>
/// <param name="kid">The kid from token."/></param>
/// <param name="currentConfiguration">The <see cref="BaseConfiguration"/> to check.</param>
/// <param name="lkgConfiguration"></param>
/// <param name="currentException"></param>
/// <param name="lkgConfiguration">The LKG exception to check.</param>
/// <param name="currentException">The exception to check.</param>
/// <returns><c>true</c> if the configuration is recoverable otherwise, <c>false</c>.</returns>
internal static bool IsRecoverableConfiguration(string kid, BaseConfiguration currentConfiguration, BaseConfiguration lkgConfiguration, Exception currentException)
{
if (currentException is SecurityTokenInvalidIssuerException || (currentException as SecurityTokenUnableToValidateException)?.ValidationFailure == ValidationFailure.InvalidIssuer)
Lazy<bool> isRecoverableIssuer = new Lazy<bool>(() => currentConfiguration.Issuer != lkgConfiguration.Issuer);
Lazy<bool> isRecoverableSigningKey = new Lazy<bool>(() => lkgConfiguration.SigningKeys.Any(signingKey => signingKey.KeyId == kid));

if (currentException is SecurityTokenInvalidIssuerException)
{
return currentConfiguration.Issuer != lkgConfiguration.Issuer;
return isRecoverableIssuer.Value;
}
else if (currentException is SecurityTokenSignatureKeyNotFoundException)
{
return isRecoverableSigningKey.Value;
}
else if ((currentException as SecurityTokenUnableToValidateException)?.ValidationFailure == ValidationFailure.InvalidIssuer)
{
return isRecoverableIssuer.Value && isRecoverableSigningKey.Value;
}
else if (currentException.GetType().Equals(typeof(SecurityTokenInvalidSignatureException)))
{
SecurityKey currentSigningKey = currentConfiguration.SigningKeys.Where(x => x.KeyId == kid).FirstOrDefault();
SecurityKey currentSigningKey = currentConfiguration.SigningKeys.FirstOrDefault(x => x.KeyId == kid);
if (currentSigningKey == null)
return true;
return isRecoverableSigningKey.Value;

SecurityKey lkgSigningKey = lkgConfiguration.SigningKeys.Where(x => x.KeyId == kid).FirstOrDefault();
return currentSigningKey.InternalId != lkgSigningKey.InternalId;
}
else if (currentException is SecurityTokenSignatureKeyNotFoundException)
{
SecurityKey lkgSigningKey = lkgConfiguration.SigningKeys.Where(x => x.KeyId == kid).FirstOrDefault();
return lkgSigningKey != null;
SecurityKey lkgSigningKey = lkgConfiguration.SigningKeys.FirstOrDefault(signingKey => signingKey.KeyId == kid);
return lkgSigningKey != null && currentSigningKey.InternalId != lkgSigningKey.InternalId;
}

return false;
Expand Down
10 changes: 6 additions & 4 deletions src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -934,13 +934,15 @@ private ClaimsPrincipal ValidateToken(string token, JwtSecurityToken outerToken,
validationParameters.RefreshBeforeValidation = false;
validationParameters.ValidateWithLKG = true;
var recoverableException = exceptionThrown.SourceException;
string kid = outerToken != null ? outerToken.Header.Kid :
(ValidateSignatureUsingDelegates(token, validationParameters, null) ?? GetJwtSecurityTokenFromToken(token, validationParameters)).Header.Kid;

foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfiguraitonFromCache().Keys)
foreach (BaseConfiguration lkgConfiguration in validationParameters.ConfigurationManager.GetValidLkgConfiguraitonFromCache())
{
if (!lkgConfiguration.Equals(currentConfiguration) && TokenUtilities.IsRecoverableConfiguration(outerToken.Header.Kid, currentConfiguration, lkgConfiguration, recoverableException))
if (!lkgConfiguration.Equals(currentConfiguration) && TokenUtilities.IsRecoverableConfiguration(kid, currentConfiguration, lkgConfiguration, recoverableException))
{
claimsPrincipal = outerToken != null ? ValidateJWE(token, outerToken, validationParameters, currentConfiguration, out signatureValidatedToken, out exceptionThrown) :
ValidateJWS(token, validationParameters, currentConfiguration, out signatureValidatedToken, out exceptionThrown);
claimsPrincipal = outerToken != null ? ValidateJWE(token, outerToken, validationParameters, lkgConfiguration, out signatureValidatedToken, out exceptionThrown) :
ValidateJWS(token, validationParameters, lkgConfiguration, out signatureValidatedToken, out exceptionThrown);

if (claimsPrincipal != null)
return claimsPrincipal;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,12 @@ public void GetSets()
{
TestUtilities.WriteHeader($"{this}.GetSets", "GetSets", true);

int ExpectedPropertyCount = 8;
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>("OpenIdConnectMetadata.json", new OpenIdConnectConfigurationRetriever(), new FileDocumentRetriever());
Type type = typeof(ConfigurationManager<OpenIdConnectConfiguration>);
PropertyInfo[] properties = type.GetProperties();
if (properties.Length != 7)
Assert.True(false, "Number of properties has changed from 7 to: " + properties.Length + ", adjust tests");
if (properties.Length != ExpectedPropertyCount)
Assert.True(false, "Number of properties has changed from {ExpectedPropertyCount} to: " + properties.Length + ", adjust tests");

var defaultAutomaticRefreshInterval = ConfigurationManager<OpenIdConnectConfiguration>.DefaultAutomaticRefreshInterval;
var defaultRefreshInterval = ConfigurationManager<OpenIdConnectConfiguration>.DefaultRefreshInterval;
Expand Down Expand Up @@ -330,6 +331,43 @@ public void ResetLastKnownGoodLifetime()
TestUtilities.AssertFailIfErrors(context);
}

[Fact]
public void TestConfigurationComparer()
{
TestUtilities.WriteHeader($"{this}.TestConfigurationComparer", "TestConfigurationComparer", true);
var context = new CompareContext();

var config1 = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer };
config1.SigningKeys.Add(KeyingMaterial.DefaultX509Key_2048);
config1.SigningKeys.Add(KeyingMaterial.DefaultRsaSecurityKey1);
config1.SigningKeys.Add(KeyingMaterial.DefaultRsaSecurityKey2);

var config2 = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer };
config2.SigningKeys.Add(KeyingMaterial.DefaultRsaSecurityKey1);
config2.SigningKeys.Add(KeyingMaterial.DefaultX509Key_2048);
config2.SigningKeys.Add(KeyingMaterial.DefaultRsaSecurityKey2);

var config3 = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer };
config3.SigningKeys.Add(KeyingMaterial.DefaultRsaSecurityKey1);

var config4 = new OpenIdConnectConfiguration() { TokenEndpoint = Default.Issuer + "oauth/token", Issuer = Default.Issuer + "1" };
config3.SigningKeys.Add(KeyingMaterial.DefaultRsaSecurityKey1);

var configurationManager = new MockConfigurationManager<OpenIdConnectConfiguration>(config1, config1);
IdentityComparer.AreEqual(configurationManager.GetValidLkgConfiguraitonFromCache().Count, 1, context);

configurationManager.LastKnownGoodConfiguration = config2;
IdentityComparer.AreEqual(configurationManager.GetValidLkgConfiguraitonFromCache().Count, 1, context);

configurationManager.LastKnownGoodConfiguration = config3;
IdentityComparer.AreEqual(configurationManager.GetValidLkgConfiguraitonFromCache().Count, 2, context);

configurationManager.LastKnownGoodConfiguration = config4;
IdentityComparer.AreEqual(configurationManager.GetValidLkgConfiguraitonFromCache().Count, 3, context);

TestUtilities.AssertFailIfErrors(context);
}

[Theory, MemberData(nameof(ValidateOpenIdConnectConfigurationTestCases), DisableDiscoveryEnumeration = true)]
public void ValidateOpenIdConnectConfigurationTests(ConfigurationManagerTheoryData<OpenIdConnectConfiguration> theoryData)
{
Expand Down
24 changes: 24 additions & 0 deletions test/Microsoft.IdentityModel.TestUtils/MockConfigurationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,30 @@ public MockConfigurationManager(T configuration, T lkgConfiguration, T refreshed
_refreshedConfiguration = refreshedConfiguration;
}

/// <summary>
/// Initializes an new instance of <see cref="MockConfigurationManager{T}"/> with a Configuration instance and a LKG Configuration instance.
/// </summary>
/// <param name="configuration">Configuration of type OpenIdConnectConfiguration or WsFederationConfiguration.</param>
/// <param name="lkgLifetime">The LKG configuration lifetime.</param>
public MockConfigurationManager(T configuration, TimeSpan lkgLifetime) : this(configuration)
{
LastKnownGoodLifetime = lkgLifetime;
}

/// <summary>
/// Initializes an new instance of <see cref="MockConfigurationManager{T}"/> with a Configuration instance and a LKG Configuration instance.
/// </summary>
/// <param name="configuration">Configuration of type OpenIdConnectConfiguration or WsFederationConfiguration.</param>
/// <param name="lkgConfiguration">Configuration of type OpenIdConnectConfiguration or WsFederationConfiguration.</param>
/// <param name="lkgLifetime">The LKG configuration lifetime.</param>
public MockConfigurationManager(T configuration, T lkgConfiguration, TimeSpan lkgLifetime) : this(configuration, lkgLifetime)
{
if (lkgConfiguration == null)
throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(lkgConfiguration)));

LastKnownGoodConfiguration = lkgConfiguration as BaseConfiguration;
}

/// <summary>
/// Obtains an updated version of Configuration.
/// </summary>
Expand Down
Loading

0 comments on commit 4cf75e3

Please sign in to comment.