diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt index ba5736133f..ff770358f8 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt @@ -1,6 +1,6 @@ -Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.SecurityToken token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> -Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler._telemetryClient -> Microsoft.IdentityModel.Telemetry.ITelemetryClient +override Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.SecurityToken token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +override Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.CreateToken(string payload, Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor tokenDescriptor) -> string static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.EncryptToken(byte[] innerTokenUtf8Bytes, Microsoft.IdentityModel.Tokens.EncryptingCredentials encryptingCredentials, string compressionAlgorithm, System.Collections.Generic.IDictionary additionalHeaderClaims, string tokenType, bool includeKeyIdInHeader) -> string static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.EncryptToken(byte[] innerTokenUtf8Bytes, Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor tokenDescriptor) -> string diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs index b807bdf291..8d9811531e 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs @@ -24,7 +24,7 @@ public partial class JsonWebTokenHandler : TokenHandler /// A that contains call information. /// A that can be used to request cancellation of the asynchronous operation. /// A with either a if the token was validated or an with the failure information and exception otherwise. - internal async Task> ValidateTokenAsync( + internal override async Task> ValidateTokenAsync( string token, ValidationParameters validationParameters, CallContext callContext, @@ -76,7 +76,7 @@ internal async Task> ValidateTokenAsync( } /// - internal async Task> ValidateTokenAsync( + internal override async Task> ValidateTokenAsync( SecurityToken token, ValidationParameters validationParameters, CallContext callContext, @@ -391,7 +391,7 @@ await ValidateJWSAsync(actorToken, actorParameters, configuration, callContext, try { issuerSigningKeyValidationResult = validationParameters.IssuerSigningKeyValidator( - jsonWebToken.SigningKey, jsonWebToken, validationParameters, configuration, callContext); + jsonWebToken.SigningKey, jsonWebToken, validationParameters, callContext); if (!issuerSigningKeyValidationResult.IsValid) return issuerSigningKeyValidationResult.UnwrapError().AddCurrentStackFrame(); diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt index a5130b6d3f..80105438bd 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt @@ -15,12 +15,13 @@ Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.SamlValidationError(Micr Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.CreateClaimsIdentity(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, string issuer) -> System.Security.Claims.ClaimsIdentity Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task> +Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.Saml2ValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException = null) -> void -override Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.CreateClaimsIdentityInternal(Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, string issuer) -> System.Security.Claims.ClaimsIdentity -override Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.CreateException() -> System.Exception -override Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.CreateClaimsIdentityInternal(Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, string issuer) -> System.Security.Claims.ClaimsIdentity +override Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.GetException() -> System.Exception +override Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.GetException() -> System.Exception override Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.CreateException() -> System.Exception +static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.SignatureValidationFailed -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateSignature(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult static Microsoft.IdentityModel.Tokens.Saml.SamlTokenUtilities.PopulateValidationParametersWithCurrentConfigurationAsync(Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task static Microsoft.IdentityModel.Tokens.Saml.SamlTokenUtilities.ResolveTokenSigningKey(Microsoft.IdentityModel.Xml.KeyInfo tokenKeyInfo, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters) -> Microsoft.IdentityModel.Tokens.SecurityKey diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs index bdf1cdff34..752c41aba1 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs @@ -24,7 +24,7 @@ public partial class SamlSecurityTokenHandler : SecurityTokenHandler /// A that contains call information. /// A that can be used to request cancellation of the asynchronous operation. /// A with either a if the token was validated or an with the failure information and exception otherwise. - internal async Task> ValidateTokenAsync( + internal async override Task> ValidateTokenAsync( string token, ValidationParameters validationParameters, CallContext callContext, @@ -43,7 +43,7 @@ internal async Task> ValidateTokenAsync( return await ValidateTokenAsync(tokenReadingResult.UnwrapResult(), validationParameters, callContext, cancellationToken).ConfigureAwait(false); } - internal async Task> ValidateTokenAsync( + internal override async Task> ValidateTokenAsync( SecurityToken securityToken, ValidationParameters validationParameters, CallContext callContext, @@ -150,7 +150,6 @@ internal async Task> ValidateTokenAsync( samlToken.SigningKey, samlToken, validationParameters, - null, callContext); if (!issuerSigningKeyValidationResult.IsValid) diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs index fbc070a505..497e62d29a 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs @@ -25,7 +25,7 @@ public partial class Saml2SecurityTokenHandler : SecurityTokenHandler /// A that contains call information. /// A that can be used to request cancellation of the asynchronous operation. /// A with either a if the token was validated or an with the failure information and exception otherwise. - internal async Task> ValidateTokenAsync( + internal override async Task> ValidateTokenAsync( string token, ValidationParameters validationParameters, CallContext callContext, @@ -44,7 +44,7 @@ internal async Task> ValidateTokenAsync( return await ValidateTokenAsync(tokenReadingResult.UnwrapResult(), validationParameters, callContext, cancellationToken).ConfigureAwait(false); } - internal async Task> ValidateTokenAsync( + internal override async Task> ValidateTokenAsync( SecurityToken securityToken, ValidationParameters validationParameters, CallContext callContext, @@ -155,7 +155,6 @@ internal async Task> ValidateTokenAsync( samlToken.SigningKey, samlToken, validationParameters, - null, callContext); if (!issuerSigningKeyValidationResult.IsValid) diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt index aa2b04cc0f..8ce71ead77 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -163,3 +163,5 @@ static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.TokenIsNotS static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.TokenReplayValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.TokenTypeValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.XmlValidationFailed -> Microsoft.IdentityModel.Tokens.ValidationFailureType +virtual Microsoft.IdentityModel.Tokens.TokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.SecurityToken token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +virtual Microsoft.IdentityModel.Tokens.TokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> diff --git a/src/Microsoft.IdentityModel.Tokens/TokenHandler.Internal.cs b/src/Microsoft.IdentityModel.Tokens/TokenHandler.Internal.cs new file mode 100644 index 0000000000..b6ba14d101 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/TokenHandler.Internal.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using System.Threading.Tasks; +using static Microsoft.IdentityModel.Logging.LogHelper; + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Defines properties shared across all security token handlers. + /// + public abstract partial class TokenHandler + { + internal virtual Task> ValidateTokenAsync( + string token, + ValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken) + { + throw LogExceptionMessage( + new NotImplementedException( + FormatInvariant( + LogMessages.IDX10267, + MarkAsNonPII("internal virtual Task> " + + "ValidateTokenAsync(string token, ValidationParameters validationParameters, CallContext callContext, CancellationToken cancellationToken)"), + MarkAsNonPII(GetType().FullName)))); + } + + internal virtual Task> ValidateTokenAsync( + SecurityToken token, + ValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken) + { + throw LogExceptionMessage( + new NotImplementedException( + FormatInvariant( + LogMessages.IDX10267, + MarkAsNonPII("internal virtual Task> " + + "ValidateTokenAsync(SecurityToken token, ValidationParameters validationParameters, CallContext callContext, CancellationToken cancellationToken)"), + MarkAsNonPII(GetType().FullName)))); + } + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/TokenHandler.cs b/src/Microsoft.IdentityModel.Tokens/TokenHandler.cs index ffb8a97232..754a87eea3 100644 --- a/src/Microsoft.IdentityModel.Tokens/TokenHandler.cs +++ b/src/Microsoft.IdentityModel.Tokens/TokenHandler.cs @@ -12,7 +12,7 @@ namespace Microsoft.IdentityModel.Tokens /// /// Defines properties shared across all security token handlers. /// - public abstract class TokenHandler + public abstract partial class TokenHandler { private int _defaultTokenLifetimeInMinutes = DefaultTokenLifetimeInMinutes; private int _maximumTokenSizeInBytes = TokenValidationParameters.DefaultMaximumTokenSizeInBytes; diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs index 6b0d151d0a..864f974793 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs @@ -22,7 +22,7 @@ internal class ValidationParameters private string _roleClaimType = ClaimsIdentity.DefaultRoleClaimType; private Dictionary? _instancePropertyBag; private IList? _issuerSigningKeys; - private IDictionary? _propertyBag; + private Dictionary? _propertyBag; private IList? _tokenDecryptionKeys; private IList? _validIssuers; private IList? _validTokenTypes; @@ -83,7 +83,9 @@ protected ValidationParameters(ValidationParameters other) LogTokenId = other.LogTokenId; NameClaimType = other.NameClaimType; NameClaimTypeRetriever = other.NameClaimTypeRetriever; - _propertyBag = other.PropertyBag; + foreach (var keyValue in other.PropertyBag) + PropertyBag[keyValue.Key] = keyValue.Value; + RefreshBeforeValidation = other.RefreshBeforeValidation; RoleClaimType = other.RoleClaimType; RoleClaimTypeRetriever = other.RoleClaimTypeRetriever; @@ -290,7 +292,9 @@ public IssuerSigningKeyValidationDelegate IssuerSigningKeyValidator /// Calling will result in a new instance of this IDictionary. /// public IDictionary InstancePropertyBag => - _instancePropertyBag ??= new Dictionary(); + _instancePropertyBag ?? + Interlocked.CompareExchange(ref _instancePropertyBag, [], null) ?? + _instancePropertyBag; /// /// Gets a value indicating if was called to obtain this instance. @@ -389,7 +393,9 @@ public string NameClaimType /// Gets or sets the that contains a collection of custom key/value pairs. /// This allows addition of parameters that could be used in custom token validation scenarios. /// - public IDictionary PropertyBag => _propertyBag ??= new Dictionary(); + public IDictionary PropertyBag => _propertyBag ?? + Interlocked.CompareExchange(ref _propertyBag, [], null) ?? + _propertyBag; /// /// A boolean to control whether configuration should be refreshed before validating a token. diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs index 1c0cfec536..6d980531df 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs @@ -14,7 +14,6 @@ namespace Microsoft.IdentityModel.Tokens /// The security key to validate. /// The that is being validated. /// The to be used for validating the token. - /// The to be used for validation. /// The to be used for logging. /// A that contains the results of validating the issuer. /// This delegate is not expected to throw. @@ -22,7 +21,6 @@ internal delegate ValidationResult IssuerSigningKey SecurityKey signingKey, SecurityToken securityToken, ValidationParameters validationParameters, - BaseConfiguration? configuration, CallContext callContext); /// @@ -36,15 +34,11 @@ public static partial class Validators /// The that signed the . /// The being validated. /// The to be used for validating the token. - /// The to be used for validation. /// The that contains call information. internal static ValidationResult ValidateIssuerSigningKey( SecurityKey securityKey, SecurityToken securityToken, ValidationParameters validationParameters, -#pragma warning disable CA1801 // Review unused parameters - BaseConfiguration? configuration, -#pragma warning restore CA1801 // Review unused parameters CallContext callContext) { if (validationParameters == null) diff --git a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.Internal.cs b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.Internal.cs new file mode 100644 index 0000000000..c1a68e0626 --- /dev/null +++ b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.Internal.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using System; +using Microsoft.IdentityModel.Tokens; +using System.Threading; +using Microsoft.IdentityModel.Logging; +using System.Diagnostics; + +namespace Microsoft.IdentityModel.Validators +{ + /// + /// Generic class that validates the issuer for either JsonWebTokens or JwtSecurityTokens issued from the Microsoft identity platform (AAD). + /// + public partial class AadIssuerValidator + { + /// + /// Validate the issuer for single and multi-tenant applications of various audiences (Work and School accounts, or Work and School accounts + + /// Personal accounts) and the various clouds. + /// + /// Issuer to validate (will be tenanted). + /// Received security token. + /// The to be used for validating the token. + /// The call context used for logging. + /// CancellationToken used to cancel call. + /// An that contains either the issuer that was validated or an error. + /// An EXACT match is required. + internal async Task> ValidateIssuerAsync( + string issuer, + SecurityToken securityToken, + ValidationParameters validationParameters, +#pragma warning disable CA1801 // Review unused parameters + CallContext callContext, + CancellationToken cancellationToken) +#pragma warning restore CA1801 // Review unused parameters + { + _ = issuer ?? throw LogHelper.LogArgumentNullException(nameof(issuer)); + _ = securityToken ?? throw LogHelper.LogArgumentNullException(nameof(securityToken)); + _ = validationParameters ?? throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + string tenantId; + + try + { + tenantId = GetTenantIdFromToken(securityToken); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + return new IssuerValidationError( + new MessageDetail( + ex.Message, + LogHelper.MarkAsNonPII(issuer)), + ValidationFailureType.IssuerValidationFailed, + typeof(SecurityTokenInvalidIssuerException), + new StackFrame(true), + issuer, + ex); + } + + if (string.IsNullOrWhiteSpace(tenantId)) + return new IssuerValidationError( + new MessageDetail( + LogMessages.IDX40003, + LogHelper.MarkAsNonPII(issuer)), + ValidationFailureType.IssuerValidationFailed, + typeof(SecurityTokenInvalidIssuerException), + new StackFrame(true), + issuer); + + for (int i = 0; i < validationParameters.ValidIssuers.Count; i++) + { + if (IsValidIssuer(validationParameters.ValidIssuers[i], tenantId, issuer)) + return new ValidatedIssuer(issuer!, IssuerValidationSource.IssuerMatchedConfiguration); + } + + try + { + var issuerVersion = GetTokenIssuerVersion(securityToken); + var effectiveConfigurationManager = GetEffectiveConfigurationManager(issuerVersion); + + string aadIssuer; + if (validationParameters.ValidateWithLKG) + { + // returns null if LKG issuer expired + aadIssuer = GetEffectiveLKGIssuer(issuerVersion); + } + else + { + var baseConfiguration = await GetBaseConfigurationAsync(effectiveConfigurationManager, validationParameters).ConfigureAwait(false); + aadIssuer = baseConfiguration.Issuer; + } + + if (aadIssuer != null) + { + var isIssuerValid = IsValidIssuer(aadIssuer, tenantId, issuer); + + // The original LKG assignment behavior for previous self-state management. + if (isIssuerValid && !validationParameters.ValidateWithLKG) + SetEffectiveLKGIssuer(aadIssuer, issuerVersion, effectiveConfigurationManager.LastKnownGoodLifetime); + + if (isIssuerValid) + return new ValidatedIssuer(issuer!, IssuerValidationSource.IssuerMatchedConfiguration); + } + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + return new IssuerValidationError( + new MessageDetail( + LogMessages.IDX40001, + LogHelper.MarkAsNonPII(issuer)), + ValidationFailureType.IssuerValidationFailed, + typeof(SecurityTokenInvalidIssuerException), + new StackFrame(true), + issuer, + ex); + } + + // If a valid issuer is not found, return an error. + return new IssuerValidationError( + new MessageDetail( + LogMessages.IDX40001, + LogHelper.MarkAsNonPII(issuer)), + ValidationFailureType.IssuerValidationFailed, + typeof(SecurityTokenInvalidIssuerException), + new StackFrame(true), + issuer); + } + + private static async Task GetBaseConfigurationAsync(BaseConfigurationManager configurationManager, ValidationParameters validationParameters) + { + if (validationParameters.RefreshBeforeValidation) + configurationManager.RequestRefresh(); + + return await configurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs index 72771086b5..46ba3de09d 100644 --- a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs +++ b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs @@ -19,7 +19,7 @@ namespace Microsoft.IdentityModel.Validators /// /// Generic class that validates the issuer for either JsonWebTokens or JwtSecurityTokens issued from the Microsoft identity platform (AAD). /// - public class AadIssuerValidator + public partial class AadIssuerValidator { private static readonly TimeSpan LastKnownGoodConfigurationLifetime = new TimeSpan(0, 24, 0, 0); @@ -492,7 +492,7 @@ private BaseConfigurationManager GetEffectiveConfigurationManager(ProtocolVersio return configurationManager; } - // If no provider or provider returned null, fallback to previous strategy + // If no provider or provider returned null, fallback to previous strategy return GetConfigurationManager(protocolVersion); } diff --git a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidatorConstants.cs b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidatorConstants.cs index f32fbcc342..e7aac386c6 100644 --- a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidatorConstants.cs +++ b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidatorConstants.cs @@ -12,6 +12,7 @@ internal class AadIssuerValidatorConstants public const string Common = "common"; public const string OidcEndpoint = "/.well-known/openid-configuration"; public const string FallbackAuthority = "https://login.microsoftonline.com/"; + public const string CloudInstanceNameKey = "cloud_instance_name"; /// /// Old TenantId claim: "http://schemas.microsoft.com/identity/claims/tenantid". diff --git a/src/Microsoft.IdentityModel.Validators/AadTokenValidationParametersExtension.cs b/src/Microsoft.IdentityModel.Validators/AadTokenValidationParametersExtension.cs index 56491d141f..58f07e8f80 100644 --- a/src/Microsoft.IdentityModel.Validators/AadTokenValidationParametersExtension.cs +++ b/src/Microsoft.IdentityModel.Validators/AadTokenValidationParametersExtension.cs @@ -16,8 +16,6 @@ namespace Microsoft.IdentityModel.Validators /// public static class AadTokenValidationParametersExtension { - private const string CloudInstanceNameKey = "cloud_instance_name"; - /// /// Enables validation of the cloud instance of the Microsoft Entra ID token signing keys. /// @@ -150,13 +148,13 @@ internal static void ValidateSigningKeyCloudInstance(SecurityKey securityKey, Ba return; JsonWebKey matchedKeyFromConfig = GetJsonWebKeyBySecurityKey(openIdConnectConfiguration, securityKey); - if (matchedKeyFromConfig != null && matchedKeyFromConfig.AdditionalData.TryGetValue(CloudInstanceNameKey, out object value)) + if (matchedKeyFromConfig != null && matchedKeyFromConfig.AdditionalData.TryGetValue(AadIssuerValidatorConstants.CloudInstanceNameKey, out object value)) { string signingKeyCloudInstanceName = value as string; if (string.IsNullOrWhiteSpace(signingKeyCloudInstanceName)) return; - if (openIdConnectConfiguration.AdditionalData.TryGetValue(CloudInstanceNameKey, out object configurationCloudInstanceNameObjectValue)) + if (openIdConnectConfiguration.AdditionalData.TryGetValue(AadIssuerValidatorConstants.CloudInstanceNameKey, out object configurationCloudInstanceNameObjectValue)) { string configurationCloudInstanceName = configurationCloudInstanceNameObjectValue as string; if (string.IsNullOrWhiteSpace(configurationCloudInstanceName)) @@ -174,7 +172,7 @@ internal static void ValidateSigningKeyCloudInstance(SecurityKey securityKey, Ba } } - private static JsonWebKey GetJsonWebKeyBySecurityKey(OpenIdConnectConfiguration configuration, SecurityKey securityKey) + internal static JsonWebKey GetJsonWebKeyBySecurityKey(OpenIdConnectConfiguration configuration, SecurityKey securityKey) { if (configuration.JsonWebKeySet == null) return null; diff --git a/src/Microsoft.IdentityModel.Validators/AadValidationParametersExtension.Internal.cs b/src/Microsoft.IdentityModel.Validators/AadValidationParametersExtension.Internal.cs new file mode 100644 index 0000000000..2fcb73b357 --- /dev/null +++ b/src/Microsoft.IdentityModel.Validators/AadValidationParametersExtension.Internal.cs @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; +using Microsoft.IdentityModel.Tokens; + +namespace Microsoft.IdentityModel.Validators +{ + /// + /// A generic class for additional validation checks on issued by the Microsoft identity platform (AAD). + /// + internal static class AadValidationParametersExtension + { + /// + /// Enables validation of the cloud instance of the Microsoft Entra ID token signing keys. + /// + /// The that are used to validate the token. + internal static void EnableEntraIdSigningKeyCloudInstanceValidation(this ValidationParameters validationParameters) + { + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + IssuerSigningKeyValidationDelegate originalIssuerSigningKeyValidationDelegate = validationParameters.IssuerSigningKeyValidator; + + IssuerSigningKeyValidationDelegate cloudInstanceSigningKeyValidationDelegate = (securityKey, securityToken, validationParameters, callContext) => + { + BaseConfiguration configuration = null; + if (validationParameters.ConfigurationManager != null) + configuration = validationParameters.ConfigurationManager.GetBaseConfigurationAsync(CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(); + + ValidateSigningKeyCloudInstance(securityKey, configuration); + + // preserve and run provided logic + if (originalIssuerSigningKeyValidationDelegate != null) + return originalIssuerSigningKeyValidationDelegate(securityKey, securityToken, validationParameters, callContext); + + return new ValidatedSigningKeyLifetime(DateTime.UtcNow, DateTime.UtcNow, DateTime.UtcNow); + }; + + validationParameters.IssuerSigningKeyValidator = cloudInstanceSigningKeyValidationDelegate; + } + + /// + /// Enables the validation of the issuer of the signing keys used by the Microsoft identity platform (AAD) against the issuer of the token. + /// + /// The that are used to validate the token. + internal static void EnableAadSigningKeyIssuerValidation(this ValidationParameters validationParameters) + { + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + IssuerSigningKeyValidationDelegate issuerSigningKeyValidationDelegate = validationParameters.IssuerSigningKeyValidator; + + validationParameters.IssuerSigningKeyValidator = (securityKey, securityToken, vp, callContext) => + { + BaseConfiguration baseConfiguration = null; + if (vp.ConfigurationManager != null) + baseConfiguration = vp.ConfigurationManager.GetBaseConfigurationAsync(System.Threading.CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult(); + + AadTokenValidationParametersExtension.ValidateIssuerSigningKey(securityKey, securityToken, baseConfiguration); + + // preserve and run provided logic + if (issuerSigningKeyValidationDelegate != null) + return issuerSigningKeyValidationDelegate(securityKey, securityToken, vp, callContext); + + return ValidateIssuerSigningKeyCertificate(securityKey, validationParameters); + }; + } + + /// + /// Validates the cloud instance of the signing key. + /// + /// The that signed the . + /// The provided. + internal static void ValidateSigningKeyCloudInstance(SecurityKey securityKey, BaseConfiguration configuration) + { + if (securityKey == null) + return; + + if (configuration is not OpenIdConnectConfiguration openIdConnectConfiguration) + return; + + JsonWebKey matchedKeyFromConfig = AadTokenValidationParametersExtension.GetJsonWebKeyBySecurityKey(openIdConnectConfiguration, securityKey); + if (matchedKeyFromConfig != null && matchedKeyFromConfig.AdditionalData.TryGetValue(AadIssuerValidatorConstants.CloudInstanceNameKey, out object value)) + { + string signingKeyCloudInstanceName = value as string; + if (string.IsNullOrWhiteSpace(signingKeyCloudInstanceName)) + return; + + if (openIdConnectConfiguration.AdditionalData.TryGetValue(AadIssuerValidatorConstants.CloudInstanceNameKey, out object configurationCloudInstanceNameObjectValue)) + { + string configurationCloudInstanceName = configurationCloudInstanceNameObjectValue as string; + if (string.IsNullOrWhiteSpace(configurationCloudInstanceName)) + return; + + if (!string.Equals(signingKeyCloudInstanceName, configurationCloudInstanceName, StringComparison.Ordinal)) + throw LogHelper.LogExceptionMessage( + new SecurityTokenInvalidCloudInstanceException( + LogHelper.FormatInvariant( + LogMessages.IDX40012, + LogHelper.MarkAsNonPII(signingKeyCloudInstanceName), + LogHelper.MarkAsNonPII(configurationCloudInstanceName))) + { + ConfigurationCloudInstanceName = configurationCloudInstanceName, + SigningKeyCloudInstanceName = signingKeyCloudInstanceName, + SigningKey = securityKey, + }); + } + } + } + + /// + /// Validates the issuer signing key certificate. + /// + /// The that signed the . + /// The that are used to validate the token. + /// true if the issuer signing key certificate is valid; otherwise, false. + internal static ValidationResult ValidateIssuerSigningKeyCertificate(SecurityKey securityKey, ValidationParameters validationParameters) + { + if (securityKey == null) + { + throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(securityKey), LogMessages.IDX40007)); + } + + return Tokens.Validators.ValidateIssuerSigningKeyLifeTime(securityKey, validationParameters, new CallContext()); + } + } +} diff --git a/src/Microsoft.IdentityModel.Validators/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Validators/InternalAPI.Unshipped.txt index e69de29bb2..7b57e22604 100644 --- a/src/Microsoft.IdentityModel.Validators/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Validators/InternalAPI.Unshipped.txt @@ -0,0 +1,9 @@ +const Microsoft.IdentityModel.Validators.AadIssuerValidatorConstants.CloudInstanceNameKey = "cloud_instance_name" -> string +Microsoft.IdentityModel.Validators.AadIssuerValidator.ValidateIssuerAsync(string issuer, Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.IdentityModel.Validators.AadValidationParametersExtension +static Microsoft.IdentityModel.Validators.AadTokenValidationParametersExtension.GetJsonWebKeyBySecurityKey(Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfiguration configuration, Microsoft.IdentityModel.Tokens.SecurityKey securityKey) -> Microsoft.IdentityModel.Tokens.JsonWebKey +static Microsoft.IdentityModel.Validators.AadValidationParametersExtension.EnableAadSigningKeyIssuerValidation(this Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters) -> void +static Microsoft.IdentityModel.Validators.AadValidationParametersExtension.EnableEntraIdSigningKeyCloudInstanceValidation(this Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters) -> void +static Microsoft.IdentityModel.Validators.AadValidationParametersExtension.ValidateIssuerSigningKey(Microsoft.IdentityModel.Tokens.SecurityKey securityKey, Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.BaseConfiguration configuration) -> bool +static Microsoft.IdentityModel.Validators.AadValidationParametersExtension.ValidateIssuerSigningKeyCertificate(Microsoft.IdentityModel.Tokens.SecurityKey securityKey, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters) -> Microsoft.IdentityModel.Tokens.ValidationResult +static Microsoft.IdentityModel.Validators.AadValidationParametersExtension.ValidateSigningKeyCloudInstance(Microsoft.IdentityModel.Tokens.SecurityKey securityKey, Microsoft.IdentityModel.Tokens.BaseConfiguration configuration) -> void \ No newline at end of file diff --git a/test/Microsoft.IdentityModel.TestUtils/Properties/AssemblyInfo.cs b/test/Microsoft.IdentityModel.TestUtils/Properties/AssemblyInfo.cs index 9822268a7b..9f18beb123 100644 --- a/test/Microsoft.IdentityModel.TestUtils/Properties/AssemblyInfo.cs +++ b/test/Microsoft.IdentityModel.TestUtils/Properties/AssemblyInfo.cs @@ -2,7 +2,9 @@ // Licensed under the MIT License. using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: CLSCompliant(false)] [assembly: ComVisible(false)] +[assembly: InternalsVisibleTo("Microsoft.IdentityModel.Validators.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/test/Microsoft.IdentityModel.TestUtils/SamlClaimsIdentityComparisonTestBase.cs b/test/Microsoft.IdentityModel.TestUtils/SamlClaimsIdentityComparisonTestBase.cs index 00f8656c52..ebbfb2587a 100644 --- a/test/Microsoft.IdentityModel.TestUtils/SamlClaimsIdentityComparisonTestBase.cs +++ b/test/Microsoft.IdentityModel.TestUtils/SamlClaimsIdentityComparisonTestBase.cs @@ -3,7 +3,6 @@ using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; using Microsoft.IdentityModel.Tokens; -using Microsoft.IdentityModel.Tokens.Saml; using System; using System.Collections.Generic; using System.Threading; diff --git a/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs index 90b49be8aa..3e94c810c1 100644 --- a/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs +++ b/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs @@ -47,7 +47,6 @@ public static class SkipValidationDelegates SecurityKey signingKey, SecurityToken securityToken, ValidationParameters validationParameters, - BaseConfiguration? configuration, CallContext callContext) { return new ValidatedSigningKeyLifetime( diff --git a/test/Microsoft.IdentityModel.TestUtils/TestUtilities.cs b/test/Microsoft.IdentityModel.TestUtils/TestUtilities.cs index 55d5b56cfa..25450bda69 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TestUtilities.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TestUtilities.cs @@ -35,6 +35,77 @@ public class GetSetContext public static class TestUtilities { + internal static ValidationParameters CreateFromTokenValidationParameters(TokenValidationParameters tokenValidationParameters) + { + ValidationParameters validationParameters = new(); + + if (tokenValidationParameters.AuthenticationType != null) + validationParameters.AuthenticationType = tokenValidationParameters.AuthenticationType; + + validationParameters.ClockSkew = tokenValidationParameters.ClockSkew; + validationParameters.ConfigurationManager = tokenValidationParameters.ConfigurationManager; + validationParameters.CryptoProviderFactory = tokenValidationParameters.CryptoProviderFactory; + validationParameters.DebugId = tokenValidationParameters.DebugId; + validationParameters.IncludeTokenOnFailedValidation = tokenValidationParameters.IncludeTokenOnFailedValidation; + validationParameters.IgnoreTrailingSlashWhenValidatingAudience = tokenValidationParameters.IgnoreTrailingSlashWhenValidatingAudience; + + //validationParameters.IssuerSigningKeyResolver = tokenValidationParameters.IssuerSigningKeyResolver; + if (tokenValidationParameters.IssuerSigningKeys != null) + foreach (SecurityKey key in tokenValidationParameters.IssuerSigningKeys) + validationParameters.IssuerSigningKeys.Add(key); + + if (tokenValidationParameters.IssuerSigningKey != null) + validationParameters.IssuerSigningKeys.Add(tokenValidationParameters.IssuerSigningKey); + + validationParameters.LogTokenId = tokenValidationParameters.LogTokenId; + validationParameters.NameClaimType = tokenValidationParameters.NameClaimType; + validationParameters.NameClaimTypeRetriever = tokenValidationParameters.NameClaimTypeRetriever; + if (tokenValidationParameters.PropertyBag != null) + foreach (var item in tokenValidationParameters.PropertyBag) + validationParameters.PropertyBag.Add(item.Key, item.Value); + + validationParameters.RefreshBeforeValidation = tokenValidationParameters.RefreshBeforeValidation; + validationParameters.RoleClaimType = tokenValidationParameters.RoleClaimType; + validationParameters.RoleClaimTypeRetriever = tokenValidationParameters.RoleClaimTypeRetriever; + validationParameters.SaveSigninToken = tokenValidationParameters.SaveSigninToken; + + if (tokenValidationParameters.TokenDecryptionKey != null) + validationParameters.TokenDecryptionKeys.Add(tokenValidationParameters.TokenDecryptionKey); + + if (tokenValidationParameters.TokenDecryptionKeys != null) + foreach (SecurityKey key in tokenValidationParameters.TokenDecryptionKeys) + validationParameters.TokenDecryptionKeys.Add(key); + + validationParameters.TokenReplayCache = tokenValidationParameters.TokenReplayCache; + validationParameters.TryAllIssuerSigningKeys = tokenValidationParameters.TryAllIssuerSigningKeys; + validationParameters.ValidateActor = tokenValidationParameters.ValidateActor; + validationParameters.ValidateWithLKG = tokenValidationParameters.ValidateWithLKG; + + if (tokenValidationParameters.ValidAlgorithms != null) + foreach (string algorithms in tokenValidationParameters.ValidAlgorithms) + validationParameters.ValidAlgorithms.Add(algorithms); + + if (tokenValidationParameters.ValidAudiences != null) + foreach (string audience in tokenValidationParameters.ValidAudiences) + validationParameters.ValidAudiences.Add(audience); + + if (tokenValidationParameters.ValidAudience != null) + validationParameters.ValidAudiences.Add(tokenValidationParameters.ValidAudience); + + if (tokenValidationParameters.ValidIssuers != null) + foreach (string issuer in tokenValidationParameters.ValidIssuers) + validationParameters.ValidIssuers.Add(issuer); + + if (tokenValidationParameters.ValidIssuer != null) + validationParameters.ValidIssuers.Add(tokenValidationParameters.ValidIssuer); + + if (tokenValidationParameters.ValidTypes != null) + foreach (string type in tokenValidationParameters.ValidTypes) + validationParameters.ValidTypes.Add(type); + + return validationParameters; + } + /// /// Calls all public instance and static properties on an object /// diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomIssuerSigningKeyValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomIssuerSigningKeyValidationDelegates.cs index 2c94cdaf92..1e596e687f 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomIssuerSigningKeyValidationDelegates.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomIssuerSigningKeyValidationDelegates.cs @@ -13,7 +13,6 @@ internal static ValidationResult CustomIssuerSignin SecurityKey signingKey, SecurityToken securityToken, ValidationParameters validationParameters, - BaseConfiguration? configuration, CallContext callContext) { // Returns a CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError @@ -30,7 +29,6 @@ internal static ValidationResult CustomIssuerSignin SecurityKey signingKey, SecurityToken securityToken, ValidationParameters validationParameters, - BaseConfiguration? configuration, CallContext callContext) { return new CustomIssuerSigningKeyValidationError( @@ -46,7 +44,6 @@ internal static ValidationResult CustomIssuerSignin SecurityKey signingKey, SecurityToken securityToken, ValidationParameters validationParameters, - BaseConfiguration? configuration, CallContext callContext) { return new CustomIssuerSigningKeyValidationError( @@ -61,7 +58,6 @@ internal static ValidationResult CustomIssuerSignin SecurityKey signingKey, SecurityToken securityToken, ValidationParameters validationParameters, - BaseConfiguration? configuration, CallContext callContext) { return new CustomIssuerSigningKeyValidationError( @@ -77,7 +73,6 @@ internal static ValidationResult CustomIssuerSignin SecurityKey signingKey, SecurityToken securityToken, ValidationParameters validationParameters, - BaseConfiguration? configuration, CallContext callContext) { return new CustomIssuerSigningKeyWithoutGetExceptionValidationOverrideError( @@ -92,7 +87,6 @@ internal static ValidationResult IssuerSigningKeyVa SecurityKey signingKey, SecurityToken securityToken, ValidationParameters validationParameters, - BaseConfiguration? configuration, CallContext callContext) { return new IssuerSigningKeyValidationError( @@ -108,7 +102,6 @@ internal static ValidationResult IssuerSigningKeyVa SecurityKey signingKey, SecurityToken securityToken, ValidationParameters validationParameters, - BaseConfiguration? configuration, CallContext callContext) { throw new CustomSecurityTokenInvalidSigningKeyException(nameof(IssuerSigningKeyValidatorThrows), null); @@ -118,7 +111,6 @@ internal static ValidationResult IssuerSigningKeyVa SecurityKey signingKey, SecurityToken securityToken, ValidationParameters validationParameters, - BaseConfiguration? configuration, CallContext callContext) { return new IssuerSigningKeyValidationError( @@ -134,7 +126,6 @@ internal static ValidationResult IssuerSigningKeyVa SecurityKey signingKey, SecurityToken securityToken, ValidationParameters validationParameters, - BaseConfiguration? configuration, CallContext callContext) { return new IssuerSigningKeyValidationError( diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/AbstractVirtualsTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/AbstractVirtualsTests.cs index de178161da..d60e7c58e3 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/AbstractVirtualsTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/AbstractVirtualsTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.IdentityModel.TestUtils; using Xunit; @@ -26,6 +27,8 @@ public async Task BaseConfigurationManager_GetBaseConfigurationAsync() catch (Exception ex) { Assert.Contains("IDX10267: 'public virtual Task GetBaseConfigurationAsync(CancellationToken cancel)'", ex.Message); + + Assert.IsAssignableFrom(ex); } } #endregion @@ -43,6 +46,8 @@ public void SignatureProvider_Sign() catch (Exception ex) { Assert.Contains("IDX10267: 'public virtual byte[] Sign(byte[] input, int offset, int count)'", ex.Message); + + Assert.IsAssignableFrom(ex); } } @@ -59,6 +64,8 @@ public void SignatureProvider_Sign_Offset() catch (Exception ex) { Assert.Contains("IDX10267: 'public virtual bool Sign(ReadOnlySpan data, Span destination, out int bytesWritten)'", ex.Message); + + Assert.IsAssignableFrom(ex); } } #endif @@ -74,7 +81,11 @@ public void SignatureProvider_Verify_Offset() } catch (Exception ex) { - Assert.Contains("IDX10267: 'public virtual bool Verify(byte[] input, int inputOffset, int inputLength, byte[] signature, int signatureOffset, int signatureLength)'", ex.Message); + Assert.Contains("IDX10267: 'public virtual bool " + + "Verify(byte[] input, int inputOffset, int inputLength, byte[] signature, int signatureOffset, int signatureLength)'", + ex.Message); + + Assert.IsAssignableFrom(ex); } } #endregion @@ -92,6 +103,8 @@ public void TokenHandler_ReadToken() catch (Exception ex) { Assert.Contains("IDX10267: 'public virtual SecurityToken ReadToken(string token)'", ex.Message); + + Assert.IsAssignableFrom(ex); } } @@ -106,7 +119,11 @@ public void TokenHandler_CreateClaimsIdentityInternal() } catch (Exception ex) { - Assert.Contains("IDX10267: 'internal virtual ClaimsIdentity CreateClaimsIdentityInternal(SecurityToken securityToken, TokenValidationParameters tokenValidationParameters, string issuer)'", ex.Message); + Assert.Contains("IDX10267: 'internal virtual ClaimsIdentity " + + "CreateClaimsIdentityInternal(SecurityToken securityToken, TokenValidationParameters tokenValidationParameters, string issuer)'", + ex.Message); + + Assert.IsAssignableFrom(ex); } } [Fact] @@ -120,7 +137,11 @@ public async Task TokenHandler_ValidateTokenAsyncString() } catch (Exception ex) { - Assert.Contains("IDX10267: 'public virtual Task ValidateTokenAsync(string token, TokenValidationParameters validationParameters)'", ex.Message); + Assert.Contains("IDX10267: 'public virtual Task " + + "ValidateTokenAsync(string token, TokenValidationParameters validationParameters)'", + ex.Message); + + Assert.IsAssignableFrom(ex); } } @@ -135,7 +156,57 @@ public async Task TokenHandler_ValidateTokenAsyncToken() } catch (Exception ex) { - Assert.Contains("IDX10267: 'public virtual Task ValidateTokenAsync(SecurityToken token, TokenValidationParameters validationParameters)'", ex.Message); + Assert.Contains("IDX10267: 'public virtual Task " + + "ValidateTokenAsync(SecurityToken token, TokenValidationParameters validationParameters)'", + ex.Message); + + Assert.IsAssignableFrom(ex); + } + } + + [Fact] + public async Task TokenHandler_ValidationParameters_ValidateTokenAsyncString() + { + TestUtilities.WriteHeader($"{this}.TokenHandler_ValidationParameters_ValidateTokenAsyncString"); + + try + { + await new DerivedTokenHandler().ValidateTokenAsync( + "token", + new ValidationParameters(), + new CallContext(), + CancellationToken.None); + } + catch (Exception ex) + { + Assert.Contains("internal virtual Task> " + + "ValidateTokenAsync(string token, ValidationParameters validationParameters, CallContext callContext, CancellationToken cancellationToken)", + ex.Message); + + Assert.IsAssignableFrom(ex); + } + } + + [Fact] + public async Task TokenHandler_ValidationParameters_ValidateTokenAsyncToken() + { + TestUtilities.WriteHeader($"{this}.TokenHandler_ValidationParameters_ValidateTokenAsyncToken"); + + try + { + await new DerivedTokenHandler().ValidateTokenAsync( + new DerivedSecurityToken(), + new ValidationParameters(), + new CallContext(), + CancellationToken.None); + } + catch (Exception ex) + { + Assert.Contains("internal virtual Task> " + + "ValidateTokenAsync(SecurityToken token, ValidationParameters validationParameters, CallContext callContext, CancellationToken cancellationToken)", + ex.Message); + + Assert.IsAssignableFrom(ex); } } #endregion diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/SigningKeyValidationResultTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/SigningKeyValidationResultTests.cs index 932d521595..4edfe8af9a 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/SigningKeyValidationResultTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/SigningKeyValidationResultTests.cs @@ -20,7 +20,6 @@ public void SecurityKey(SigningKeyValidationTheoryData theoryData) theoryData.SecurityKey, theoryData.SecurityToken, theoryData.ValidationParameters, - theoryData.BaseConfiguration, new CallContext()); if (result.IsValid) diff --git a/test/Microsoft.IdentityModel.Validators.Tests/AadTokenValidationParametersExtensionTests.cs b/test/Microsoft.IdentityModel.Validators.Tests/AadTokenValidationParametersExtensionTests.cs index 867dc51ae8..c493a0b6ca 100644 --- a/test/Microsoft.IdentityModel.Validators.Tests/AadTokenValidationParametersExtensionTests.cs +++ b/test/Microsoft.IdentityModel.Validators.Tests/AadTokenValidationParametersExtensionTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; +using System.Threading; using System.Threading.Tasks; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Protocols; @@ -14,8 +15,6 @@ using Microsoft.IdentityModel.Tokens.Saml2; using Xunit; -#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant - namespace Microsoft.IdentityModel.Validators.Tests { // Serialize as one of the tests depends on static state (app context) @@ -28,16 +27,33 @@ public async Task EnableEntraIdSigningKeyCloudInstanceValidationTests(EnableEntr var context = TestUtilities.WriteHeader($"{this}.EnableAadSigningKeyValidationTests", theoryData); try { + + ValidationParameters validationParameters = TestUtilities.CreateFromTokenValidationParameters(theoryData.TokenValidationParameters); + validationParameters.AudienceValidator = SkipValidationDelegates.SkipAudienceValidation; + validationParameters.IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation; + // set delegates + bool validationParametersDelegateSet = false; bool delegateSet = false; + if (theoryData.SetDelegateUsingConfig) { theoryData.TokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration = (securityKey, securityToken, tvp, config) => { delegateSet = true; return true; }; + validationParameters.IssuerSigningKeyValidator = (securityKey, securityToken, validationParameters, callContext) => + { + validationParametersDelegateSet = true; + return new ValidatedSigningKeyLifetime(DateTime.UtcNow, DateTime.UtcNow, DateTime.UtcNow); + }; } else if (theoryData.SetDelegateWithoutConfig) { theoryData.TokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration = null; theoryData.TokenValidationParameters.IssuerSigningKeyValidator = (securityKey, securityToken, tvp) => { delegateSet = true; return true; }; + validationParameters.IssuerSigningKeyValidator = (securityKey, securityToken, validationParameters, callContext) => + { + validationParametersDelegateSet = true; + return new ValidatedSigningKeyLifetime(DateTime.UtcNow, DateTime.UtcNow, DateTime.UtcNow); + }; } var handler = new JsonWebTokenHandler(); @@ -45,13 +61,18 @@ public async Task EnableEntraIdSigningKeyCloudInstanceValidationTests(EnableEntr theoryData.TokenValidationParameters.EnableEntraIdSigningKeyCloudInstanceValidation(); var validationResult = await handler.ValidateTokenAsync(theoryData.Token, theoryData.TokenValidationParameters); + ValidationResult validatedToken = await handler.ValidateTokenAsync(theoryData.Token, validationParameters, new CallContext(), CancellationToken.None); + theoryData.ExpectedException.ProcessNoException(context); Assert.NotNull(theoryData.TokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration); Assert.Equal(theoryData.ExpectedValidationResult, validationResult.IsValid); // verify delegates were executed if (theoryData.ExpectedValidationResult && (theoryData.SetDelegateUsingConfig || theoryData.SetDelegateWithoutConfig)) + { Assert.True(delegateSet); + Assert.True(validationParametersDelegateSet); + } } catch (Exception ex) { @@ -165,16 +186,32 @@ public async Task EnableAadSigningKeyIssuerValidationTests(EnableEntraIdSigningK var context = TestUtilities.WriteHeader($"{this}.EnableAadSigningKeyIssuerValidationTests", theoryData); try { + ValidationParameters validationParameters = TestUtilities.CreateFromTokenValidationParameters(theoryData.TokenValidationParameters); + validationParameters.AudienceValidator = SkipValidationDelegates.SkipAudienceValidation; + validationParameters.IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation; + // set delegates bool delegateSet = false; + bool validationParametersDelegateSet = false; if (theoryData.SetDelegateUsingConfig) { theoryData.TokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration = (securityKey, securityToken, tvp, config) => { delegateSet = true; return true; }; + validationParameters.IssuerSigningKeyValidator = (securityKey, securityToken, validationParameters, callContext) => + { + validationParametersDelegateSet = true; + return new ValidatedSigningKeyLifetime(DateTime.UtcNow, DateTime.UtcNow, DateTime.UtcNow); + }; } else if (theoryData.SetDelegateWithoutConfig) { theoryData.TokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration = null; theoryData.TokenValidationParameters.IssuerSigningKeyValidator = (securityKey, securityToken, tvp) => { delegateSet = true; return true; }; + validationParameters.IssuerSigningKeyValidator = (securityKey, securityToken, validationParameters, callContext) => + { + validationParametersDelegateSet = true; + return new ValidatedSigningKeyLifetime(DateTime.UtcNow, DateTime.UtcNow, DateTime.UtcNow); + }; + } var handler = new JsonWebTokenHandler(); @@ -183,13 +220,18 @@ public async Task EnableAadSigningKeyIssuerValidationTests(EnableEntraIdSigningK theoryData.TokenValidationParameters.EnableAadSigningKeyIssuerValidation(); var validationResult = await handler.ValidateTokenAsync(jwt, theoryData.TokenValidationParameters); + ValidationResult validatedToken = await handler.ValidateTokenAsync(jwt, validationParameters, new CallContext(), CancellationToken.None); theoryData.ExpectedException.ProcessNoException(context); + Assert.NotNull(theoryData.TokenValidationParameters.IssuerSigningKeyValidatorUsingConfiguration); Assert.True(validationResult.IsValid); // verify delegates were executed if (theoryData.SetDelegateUsingConfig || theoryData.SetDelegateWithoutConfig) + { Assert.True(delegateSet); + Assert.True(validationParametersDelegateSet); + } } catch (Exception ex) { @@ -701,5 +743,3 @@ public class AadSigningKeyTheoryData : TheoryDataBase } } } - -#pragma warning restore CS3016 // Arrays as attribute arguments is not CLS-compliant diff --git a/test/Microsoft.IdentityModel.Validators.Tests/MicrosoftIdentityIssuerValidatorTest.cs b/test/Microsoft.IdentityModel.Validators.Tests/MicrosoftIdentityIssuerValidatorTest.cs index 908ae78870..fb9311cea9 100644 --- a/test/Microsoft.IdentityModel.Validators.Tests/MicrosoftIdentityIssuerValidatorTest.cs +++ b/test/Microsoft.IdentityModel.Validators.Tests/MicrosoftIdentityIssuerValidatorTest.cs @@ -8,6 +8,7 @@ using System.Net.Http; using System.Security.Claims; using System.Threading; +using System.Threading.Tasks; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.TestUtils; @@ -141,7 +142,7 @@ public void GetIssuerValidator_CachedAuthority_ReturnsCachedValidator() } [Fact] - public void Validate_NullOrEmptyParameters_ThrowsException() + public async Task Validate_NullOrEmptyParameters_ThrowsException() { var context = new CompareContext(); var validator = new AadIssuerValidator(_httpClient, ValidatorConstants.AadIssuer); @@ -149,14 +150,22 @@ public void Validate_NullOrEmptyParameters_ThrowsException() var validationParams = new TokenValidationParameters(); Assert.Throws(ValidatorConstants.Issuer, () => validator.Validate(null, jwtSecurityToken, validationParams)); + await Assert.ThrowsAsync(async () => await ValidateIssuerAsync(null, jwtSecurityToken, validator)); var exception = Assert.Throws(() => validator.Validate(string.Empty, jwtSecurityToken, validationParams)); + ValidationResult validatedIssuer = await ValidateIssuerAsync(string.Empty, jwtSecurityToken, validator); + Assert.False(validatedIssuer.IsValid); IdentityComparer.AreEqual(LogMessages.IDX40003, exception.Message); Assert.Throws(ValidatorConstants.SecurityToken, () => validator.Validate(ValidatorConstants.AadIssuer, null, validationParams)); + await Assert.ThrowsAsync(async () => await ValidateIssuerAsync(ValidatorConstants.AadIssuer, null, validator)); Assert.Throws(ValidatorConstants.ValidationParameters, () => validator.Validate(ValidatorConstants.AadIssuer, jwtSecurityToken, null)); + + await Assert.ThrowsAsync(async () => + await validator.ValidateIssuerAsync(ValidatorConstants.AadIssuer, jwtSecurityToken, null, new Tokens.CallContext(), CancellationToken.None)); + TestUtilities.AssertFailIfErrors(context); } @@ -190,7 +199,7 @@ public void Validate_NullOrEmptyTenantId_ThrowsException() [InlineData(ValidatorConstants.TenantId, ValidatorConstants.AuthorityCommonTenant, ValidatorConstants.AadIssuer, true)] [InlineData(ValidatorConstants.ClaimNameTid, ValidatorConstants.UsGovTenantId, ValidatorConstants.UsGovIssuer, true)] [InlineData(ValidatorConstants.TenantId, ValidatorConstants.UsGovTenantId, ValidatorConstants.UsGovIssuer, true)] - public void Validate_IssuerMatchedInValidIssuer_ReturnsIssuer(string tidClaimType, string tenantId, string issuer, bool useConfigurationManagerProvider) + public async Task Validate_IssuerMatchedInValidIssuer_ReturnsIssuer(string tidClaimType, string tenantId, string issuer, bool useConfigurationManagerProvider) { var context = new CompareContext(); AadIssuerValidator validator = null; @@ -200,14 +209,13 @@ public void Validate_IssuerMatchedInValidIssuer_ReturnsIssuer(string tidClaimTyp validator = new AadIssuerValidator(_httpClient, issuer, x => null); var tidClaim = new Claim(tidClaimType, tenantId); - var issClaim = new Claim(ValidatorConstants.ClaimNameIss, issuer); var jwtSecurityToken = new JwtSecurityToken(issuer: issuer, claims: new[] { issClaim, tidClaim }); - validator.AadIssuerV2 = issuer; - var actualIssuer = validator.Validate(issuer, jwtSecurityToken, new TokenValidationParameters() { ValidIssuer = issuer }); + ValidationResult validatedIssuer = await ValidateIssuerAsync(issuer, issuer, jwtSecurityToken, validator); + IdentityComparer.AreEqual(validatedIssuer.Result.Issuer, actualIssuer, context); IdentityComparer.AreEqual(issuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); } @@ -217,7 +225,7 @@ public void Validate_IssuerMatchedInValidIssuer_ReturnsIssuer(string tidClaimTyp [InlineData(ValidatorConstants.TenantId, ValidatorConstants.TenantIdAsGuid, ValidatorConstants.AadIssuer)] [InlineData(ValidatorConstants.ClaimNameTid, ValidatorConstants.TenantIdAsGuid, ValidatorConstants.V1Issuer)] [InlineData(ValidatorConstants.TenantId, ValidatorConstants.TenantIdAsGuid, ValidatorConstants.V1Issuer)] - public void Validate_NoHttpclientFactory_ReturnsIssuer(string tidClaimType, string tenantId, string issuer) + public async Task Validate_NoHttpclientFactory_ReturnsIssuer(string tidClaimType, string tenantId, string issuer) { var context = new CompareContext(); var validator = new AadIssuerValidator(null, issuer); @@ -226,8 +234,13 @@ public void Validate_NoHttpclientFactory_ReturnsIssuer(string tidClaimType, stri var issClaim = new Claim(ValidatorConstants.ClaimNameIss, issuer); var jwtSecurityToken = new JwtSecurityToken(issuer: issuer, claims: new[] { issClaim, tidClaim }); - var tokenValidationParams = new TokenValidationParameters() { ConfigurationManager = new MockConfigurationManager(new OpenIdConnectConfiguration() { Issuer = issuer }) }; + MockConfigurationManager configurationManager = + new MockConfigurationManager(new OpenIdConnectConfiguration() { Issuer = issuer }); + + var tokenValidationParams = new TokenValidationParameters() { ConfigurationManager = configurationManager }; + ValidationResult validatedIssuer = await ValidateIssuerAsync(issuer, configurationManager, jwtSecurityToken, validator); + IdentityComparer.AreEqual(issuer, validatedIssuer.Result.Issuer, context); IdentityComparer.AreEqual(issuer, validator.Validate(issuer, jwtSecurityToken, tokenValidationParams), context); TestUtilities.AssertFailIfErrors(context); } @@ -237,7 +250,7 @@ public void Validate_NoHttpclientFactory_ReturnsIssuer(string tidClaimType, stri [InlineData(ValidatorConstants.TenantId, ValidatorConstants.TenantIdAsGuid, ValidatorConstants.V1Issuer, false)] [InlineData(ValidatorConstants.ClaimNameTid, ValidatorConstants.TenantIdAsGuid, ValidatorConstants.V1Issuer, true)] [InlineData(ValidatorConstants.TenantId, ValidatorConstants.TenantIdAsGuid, ValidatorConstants.V1Issuer, true)] - public void Validate_IssuerMatchedInValidV1Issuer_ReturnsIssuer(string tidClaimType, string tenantId, string issuer, bool useConfigurationProvider) + public async Task Validate_IssuerMatchedInValidV1Issuer_ReturnsIssuer(string tidClaimType, string tenantId, string issuer, bool useConfigurationProvider) { var context = new CompareContext(); @@ -252,10 +265,10 @@ public void Validate_IssuerMatchedInValidV1Issuer_ReturnsIssuer(string tidClaimT var issClaim = new Claim(ValidatorConstants.ClaimNameIss, issuer); var jwtSecurityToken = new JwtSecurityToken(issuer: issuer, claims: new[] { issClaim, tidClaim }); - validator.AadIssuerV1 = issuer; - var actualIssuer = validator.Validate(issuer, jwtSecurityToken, new TokenValidationParameters() { ValidIssuer = issuer }); + ValidationResult validatedIssuer = await ValidateIssuerAsync(issuer, issuer, jwtSecurityToken, validator); + IdentityComparer.AreEqual(issuer, validatedIssuer.Result.Issuer, context); IdentityComparer.AreEqual(issuer, actualIssuer, context); var actualIssuers = validator.Validate(issuer, jwtSecurityToken, new TokenValidationParameters() { ValidIssuers = new[] { issuer } }); @@ -269,7 +282,7 @@ public void Validate_IssuerMatchedInValidV1Issuer_ReturnsIssuer(string tidClaimT [InlineData(ValidatorConstants.TenantId, false)] [InlineData(ValidatorConstants.ClaimNameTid, true)] [InlineData(ValidatorConstants.TenantId, true)] - public void Validate_IssuerMatchedInValidIssuers_ReturnsIssuer(string tidClaimType, bool useConfigurationProvider) + public async Task Validate_IssuerMatchedInValidIssuers_ReturnsIssuer(string tidClaimType, bool useConfigurationProvider) { var context = new CompareContext(); @@ -285,11 +298,18 @@ public void Validate_IssuerMatchedInValidIssuers_ReturnsIssuer(string tidClaimTy var jwtSecurityToken = new JwtSecurityToken(issuer: ValidatorConstants.AadIssuer, claims: new[] { issClaim, tidClaim }); var actualIssuers = validator.Validate(ValidatorConstants.AadIssuer, jwtSecurityToken, new TokenValidationParameters() { ValidIssuers = new[] { ValidatorConstants.AadIssuer } }); - IdentityComparer.AreEqual(ValidatorConstants.AadIssuer, actualIssuers, context); + ValidationResult validatedIssuerResult = await ValidateIssuerAsync( + ValidatorConstants.AadIssuer, + ValidatorConstants.AadIssuer, + jwtSecurityToken, + validator); + + Assert.True(validatedIssuerResult.IsValid); var actualIssuer = validator.Validate(ValidatorConstants.AadIssuer, jwtSecurityToken, new TokenValidationParameters() { ValidIssuer = ValidatorConstants.AadIssuer }); + IdentityComparer.AreEqual(ValidatorConstants.AadIssuer, validatedIssuerResult.Result.Issuer, context); IdentityComparer.AreEqual(ValidatorConstants.AadIssuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); } @@ -328,7 +348,7 @@ public void Validate_IssuerNotInTokenValidationParameters_ReturnsIssuer(string t [InlineData(ValidatorConstants.TenantId, ValidatorConstants.AadIssuer, true)] [InlineData(ValidatorConstants.ClaimNameTid, ValidatorConstants.V1Issuer, true)] [InlineData(ValidatorConstants.TenantId, ValidatorConstants.V1Issuer, true)] - public void ValidateJsonWebToken_ReturnsIssuer(string tidClaimType, string issuer, bool useConfigurationProvider) + public async Task ValidateJsonWebToken_ReturnsIssuer(string tidClaimType, string issuer, bool useConfigurationProvider) { AadIssuerValidator validator = null; if (useConfigurationProvider == false) @@ -346,7 +366,13 @@ public void ValidateJsonWebToken_ReturnsIssuer(string tidClaimType, string issue var jsonWebToken = new JsonWebToken(Default.Jwt(Default.SecurityTokenDescriptor(Default.SymmetricSigningCredentials, claims))); var actualIssuer = validator.Validate(issuer, jsonWebToken, new TokenValidationParameters()); + ValidationResult validatedIssuerResult = await ValidateIssuerAsync( + issuer, + jsonWebToken, + validator); + Assert.True(validatedIssuerResult.IsValid); + IdentityComparer.AreEqual(issuer, validatedIssuerResult.Result.Issuer, context); IdentityComparer.AreEqual(issuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); } @@ -356,7 +382,7 @@ public void ValidateJsonWebToken_ReturnsIssuer(string tidClaimType, string issue [InlineData(ValidatorConstants.TenantId, false)] [InlineData(ValidatorConstants.ClaimNameTid, true)] [InlineData(ValidatorConstants.TenantId, true)] - public void Validate_V1IssuerNotInTokenValidationParameters_ReturnsV1Issuer(string tidClaimType, bool useConfigurationProvider) + public async Task Validate_V1IssuerNotInTokenValidationParameters_ReturnsV1Issuer(string tidClaimType, bool useConfigurationProvider) { AadIssuerValidator validator = null; if (useConfigurationProvider == false) @@ -371,13 +397,19 @@ public void Validate_V1IssuerNotInTokenValidationParameters_ReturnsV1Issuer(stri var jwtSecurityToken = new JwtSecurityToken(issuer: ValidatorConstants.V1Issuer, claims: new[] { issClaim, tidClaim }); var actualIssuer = validator.Validate(ValidatorConstants.V1Issuer, jwtSecurityToken, new TokenValidationParameters()); + ValidationResult validatedIssuer = await ValidateIssuerAsync( + ValidatorConstants.V1Issuer, + jwtSecurityToken, + validator); + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(ValidatorConstants.V1Issuer, validatedIssuer.Result.Issuer, context); IdentityComparer.AreEqual(ValidatorConstants.V1Issuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); } [Fact] - public void Validate_TenantIdInIssuerNotInToken_ReturnsIssuer() + public async Task Validate_TenantIdInIssuerNotInToken_ReturnsIssuer() { var context = new CompareContext(); var validator = new AadIssuerValidator(_httpClient, ValidatorConstants.AadIssuer); @@ -385,13 +417,20 @@ public void Validate_TenantIdInIssuerNotInToken_ReturnsIssuer() var jwtSecurityToken = new JwtSecurityToken(issuer: ValidatorConstants.AadIssuer, claims: new[] { issClaim }); var actualIssuer = validator.Validate(ValidatorConstants.AadIssuer, jwtSecurityToken, new TokenValidationParameters() { ValidIssuer = ValidatorConstants.AadIssuer }); - + ValidationResult validatedIssuer = await ValidateIssuerAsync( + ValidatorConstants.AadIssuer, + ValidatorConstants.AadIssuer, + jwtSecurityToken, + validator); + + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(ValidatorConstants.AadIssuer, validatedIssuer.Result.Issuer, context); IdentityComparer.AreEqual(ValidatorConstants.AadIssuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); } [Fact] - public void Validate_TidClaimInToken_ReturnsIssuer() + public async Task Validate_TidClaimInToken_ReturnsIssuer() { var context = new CompareContext(); var validator = new AadIssuerValidator(_httpClient, ValidatorConstants.AadIssuer); @@ -401,11 +440,27 @@ public void Validate_TidClaimInToken_ReturnsIssuer() var jsonWebToken = new JsonWebToken($"{{}}", $"{{\"{ValidatorConstants.ClaimNameIss}\":\"{ValidatorConstants.AadIssuer}\",\"{ValidatorConstants.ClaimNameTid}\":\"{ValidatorConstants.TenantIdAsGuid}\"}}"); var actualIssuer = validator.Validate(ValidatorConstants.AadIssuer, jwtSecurityToken, new TokenValidationParameters() { ValidIssuer = ValidatorConstants.AadIssuer }); + ValidationResult validatedIssuer = await ValidateIssuerAsync( + ValidatorConstants.AadIssuer, + ValidatorConstants.AadIssuer, + jwtSecurityToken, + validator); + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(ValidatorConstants.AadIssuer, validatedIssuer.Result.Issuer, context); IdentityComparer.AreEqual(ValidatorConstants.AadIssuer, actualIssuer, context); - actualIssuer = validator.Validate(ValidatorConstants.AadIssuer, jsonWebToken, new TokenValidationParameters() { ValidIssuer = ValidatorConstants.AadIssuer }); + IdentityComparer.AreEqual(ValidatorConstants.AadIssuer, actualIssuer, context); + actualIssuer = validator.Validate(ValidatorConstants.AadIssuer, jsonWebToken, new TokenValidationParameters() { ValidIssuer = ValidatorConstants.AadIssuer }); + validatedIssuer = await ValidateIssuerAsync( + ValidatorConstants.AadIssuer, + ValidatorConstants.AadIssuer, + jsonWebToken, + validator); + + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(ValidatorConstants.AadIssuer, validatedIssuer.Result.Issuer, context); IdentityComparer.AreEqual(ValidatorConstants.AadIssuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); } @@ -413,7 +468,7 @@ public void Validate_TidClaimInToken_ReturnsIssuer() // Regression test for https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2/issues/68 // Similar to Validate_NotMatchedToMultipleIssuers_ThrowsException but uses B2C values [Fact] - public void Validate_InvalidIssuerToValidate_ThrowsException() + public async Task Validate_InvalidIssuerToValidate_ThrowsException() { var context = new CompareContext(); string invalidIssuerToValidate = $"https://badissuer/{ValidatorConstants.TenantIdAsGuid}/v2.0"; @@ -428,12 +483,23 @@ public void Validate_InvalidIssuerToValidate_ThrowsException() var exception = Assert.Throws(() => validator.Validate(invalidIssuerToValidate, jwtSecurityToken, new TokenValidationParameters() { ValidIssuers = new[] { ValidatorConstants.AadIssuer } })); + + ValidationResult validatedIssuer = await ValidateIssuerAsync( + invalidIssuerToValidate, + ValidatorConstants.AadIssuer, + jwtSecurityToken, + validator); + + Assert.False(validatedIssuer.IsValid); + IdentityComparer.AreEqual(typeof(SecurityTokenInvalidIssuerException).ToString(), validatedIssuer.Error.ExceptionType.ToString(), context); + IdentityComparer.AreEqual(expectedErrorMessage, validatedIssuer.Error.MessageDetail.Message, context); + IdentityComparer.AreEqual(expectedErrorMessage, exception.Message, context); TestUtilities.AssertFailIfErrors(context); } [Fact] - public void Validate_FromB2CAuthority_WithNoTidClaim_ValidateSuccessfully() + public async Task Validate_FromB2CAuthority_WithNoTidClaim_ValidateSuccessfully() { var context = new CompareContext(); Claim issClaim = new Claim(ValidatorConstants.ClaimNameIss, ValidatorConstants.B2CIssuer); @@ -442,13 +508,24 @@ public void Validate_FromB2CAuthority_WithNoTidClaim_ValidateSuccessfully() AadIssuerValidator validator = CreateIssuerValidator(ValidatorConstants.B2CAuthorityWithV2); - validator.Validate( + string issuer = validator.Validate( ValidatorConstants.B2CIssuer, jwtSecurityToken, new TokenValidationParameters() { ValidIssuers = new[] { ValidatorConstants.B2CIssuer }, }); + + ValidationResult validatedIssuer = await ValidateIssuerAsync( + ValidatorConstants.B2CIssuer, + ValidatorConstants.B2CIssuer, + jwtSecurityToken, + validator); + + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(ValidatorConstants.B2CIssuer, validatedIssuer.Result.Issuer, context); + IdentityComparer.AreEqual(ValidatorConstants.B2CIssuer, issuer, context); + IdentityComparer.AreEqual(ValidatorConstants.B2CAuthority, validator.AadAuthorityV1, context); IdentityComparer.AreEqual(ValidatorConstants.B2CAuthorityWithV2, validator.AadAuthorityV2, context); IdentityComparer.AreEqual(ProtocolVersion.V2, validator.AadAuthorityVersion, context); @@ -456,29 +533,44 @@ public void Validate_FromB2CAuthority_WithNoTidClaim_ValidateSuccessfully() } [Fact] - public void Validate_FromB2CAuthority_WithTokenValidateParametersValidIssuersUnspecified_ValidateSuccessfully() + public async Task Validate_FromB2CAuthority_WithTokenValidateParametersValidIssuersUnspecified_ValidateSuccessfully() { var context = new CompareContext(); var issClaim = new Claim(ValidatorConstants.ClaimNameIss, ValidatorConstants.B2CIssuer); var tfpClaim = new Claim(ValidatorConstants.ClaimNameTfp, ValidatorConstants.B2CSignUpSignInUserFlow); var jwtSecurityToken = new JwtSecurityToken(issuer: ValidatorConstants.B2CIssuer, claims: new[] { issClaim, tfpClaim }); + BaseConfigurationManager configurationManager = new MockConfigurationManager(new OpenIdConnectConfiguration() + { + Issuer = ValidatorConstants.B2CIssuer + }); var validator = new AadIssuerValidator(null, ValidatorConstants.B2CAuthority); var tokenValidationParams = new TokenValidationParameters() { - ConfigurationManager = new MockConfigurationManager(new OpenIdConnectConfiguration() - { - Issuer = ValidatorConstants.B2CIssuer - }) + ConfigurationManager = configurationManager }; - IdentityComparer.AreEqual(ValidatorConstants.B2CIssuer, validator.Validate(ValidatorConstants.B2CIssuer, jwtSecurityToken, tokenValidationParams), context); + string issuer = validator.Validate( + ValidatorConstants.B2CIssuer, + jwtSecurityToken, + tokenValidationParams); + + ValidationResult validatedIssuer = await ValidateIssuerAsync( + ValidatorConstants.B2CIssuer, + configurationManager, + jwtSecurityToken, + validator); + + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(ValidatorConstants.B2CIssuer, validatedIssuer.Result.Issuer, context); + IdentityComparer.AreEqual(ValidatorConstants.B2CIssuer, issuer, context); + TestUtilities.AssertFailIfErrors(context); } [Fact] - public void Validate_FromB2CAuthority_WithTidClaim_ValidateSuccessfully() + public async Task Validate_FromB2CAuthority_WithTidClaim_ValidateSuccessfully() { var context = new CompareContext(); Claim issClaim = new Claim(ValidatorConstants.ClaimNameIss, ValidatorConstants.B2CIssuer); @@ -488,13 +580,24 @@ public void Validate_FromB2CAuthority_WithTidClaim_ValidateSuccessfully() AadIssuerValidator validator = CreateIssuerValidator(ValidatorConstants.B2CAuthorityWithV2); - validator.Validate( + string issuer = validator.Validate( ValidatorConstants.B2CIssuer, jwtSecurityToken, new TokenValidationParameters() { ValidIssuers = new[] { ValidatorConstants.B2CIssuer }, }); + + ValidationResult validatedIssuer = await ValidateIssuerAsync( + ValidatorConstants.B2CIssuer, + ValidatorConstants.B2CIssuer, + jwtSecurityToken, + validator); + + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(ValidatorConstants.B2CIssuer, validatedIssuer.Result.Issuer, context); + IdentityComparer.AreEqual(ValidatorConstants.B2CIssuer, issuer, context); + IdentityComparer.AreEqual(ValidatorConstants.B2CAuthority, validator.AadAuthorityV1, context); IdentityComparer.AreEqual(ValidatorConstants.B2CAuthorityWithV2, validator.AadAuthorityV2, context); IdentityComparer.AreEqual(ProtocolVersion.V2, validator.AadAuthorityVersion, context); @@ -502,7 +605,7 @@ public void Validate_FromB2CAuthority_WithTidClaim_ValidateSuccessfully() } [Fact] - public void Validate_FromB2CAuthority_InvalidIssuer_Fails() + public async Task Validate_FromB2CAuthority_InvalidIssuer_Fails() { var context = new CompareContext(); Claim issClaim = new Claim(ValidatorConstants.ClaimNameIss, ValidatorConstants.B2CIssuer2); @@ -519,12 +622,22 @@ public void Validate_FromB2CAuthority_InvalidIssuer_Fails() { ValidIssuers = new[] { ValidatorConstants.B2CIssuer }, })); - IdentityComparer.AreEqual(string.Format(LogMessages.IDX40001, ValidatorConstants.B2CIssuer2), exception.Message, context); + + ValidationResult validatedIssuer = await ValidateIssuerAsync( + ValidatorConstants.B2CIssuer2, + ValidatorConstants.B2CIssuer, + jwtSecurityToken, + validator); + + string expectedMessage = string.Format(LogMessages.IDX40001, ValidatorConstants.B2CIssuer2); + IdentityComparer.AreEqual(typeof(SecurityTokenInvalidIssuerException).ToString(), validatedIssuer.Error.ExceptionType.ToString(), context); + IdentityComparer.AreEqual(expectedMessage, validatedIssuer.Error.MessageDetail.Message, context); + IdentityComparer.AreEqual(expectedMessage, exception.Message, context); TestUtilities.AssertFailIfErrors(context); } [Fact] - public void Validate_FromB2CAuthority_InvalidIssuerTid_Fails() + public async Task Validate_FromB2CAuthority_InvalidIssuerTid_Fails() { var context = new CompareContext(); string issuerWithInvalidTid = ValidatorConstants.B2CInstance + "/" + ValidatorConstants.TenantIdAsGuid + "/v2.0"; @@ -543,12 +656,21 @@ public void Validate_FromB2CAuthority_InvalidIssuerTid_Fails() ValidIssuers = new[] { ValidatorConstants.B2CIssuer }, })); - IdentityComparer.AreEqual(string.Format(LogMessages.IDX40001, issuerWithInvalidTid), exception.Message, context); + ValidationResult validatedIssuer = await ValidateIssuerAsync( + issuerWithInvalidTid, + ValidatorConstants.B2CIssuer, + jwtSecurityToken, + validator); + + string expectedMessage = string.Format(LogMessages.IDX40001, issuerWithInvalidTid); + IdentityComparer.AreEqual(typeof(SecurityTokenInvalidIssuerException).ToString(), validatedIssuer.Error.ExceptionType.ToString(), context); + IdentityComparer.AreEqual(expectedMessage, validatedIssuer.Error.MessageDetail.Message, context); + IdentityComparer.AreEqual(expectedMessage, exception.Message, context); TestUtilities.AssertFailIfErrors(context); } [Fact] - public void Validate_FromCustomB2CAuthority_ValidateSuccessfully() + public async Task Validate_FromCustomB2CAuthority_ValidateSuccessfully() { var context = new CompareContext(); Claim issClaim = new Claim(ValidatorConstants.ClaimNameIss, ValidatorConstants.B2CCustomDomainIssuer); @@ -557,7 +679,7 @@ public void Validate_FromCustomB2CAuthority_ValidateSuccessfully() AadIssuerValidator validator = CreateIssuerValidator(ValidatorConstants.B2CCustomDomainAuthorityWithV2); - validator.Validate( + string issuer = validator.Validate( ValidatorConstants.B2CCustomDomainIssuer, jwtSecurityToken, new TokenValidationParameters() @@ -565,6 +687,16 @@ public void Validate_FromCustomB2CAuthority_ValidateSuccessfully() ValidIssuers = new[] { ValidatorConstants.B2CCustomDomainIssuer }, }); + ValidationResult validatedIssuer = await ValidateIssuerAsync( + ValidatorConstants.B2CCustomDomainIssuer, + ValidatorConstants.B2CCustomDomainIssuer, + jwtSecurityToken, + validator); + + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(ValidatorConstants.B2CCustomDomainIssuer, validatedIssuer.Result.Issuer, context); + IdentityComparer.AreEqual(ValidatorConstants.B2CCustomDomainIssuer, issuer, context); + IdentityComparer.AreEqual(ValidatorConstants.B2CCustomDomainAuthority, validator.AadAuthorityV1, context); IdentityComparer.AreEqual(ValidatorConstants.B2CCustomDomainAuthorityWithV2, validator.AadAuthorityV2, context); IdentityComparer.AreEqual(ProtocolVersion.V2, validator.AadAuthorityVersion, context); @@ -572,7 +704,7 @@ public void Validate_FromCustomB2CAuthority_ValidateSuccessfully() } [Fact] - public void Validate_FromB2CAuthority_WithTfpIssuer_ThrowsException() + public async Task Validate_FromB2CAuthority_WithTfpIssuer_ThrowsException() { var context = new CompareContext(); Claim issClaim = new Claim(ValidatorConstants.ClaimNameIss, ValidatorConstants.B2CIssuerTfp); @@ -589,6 +721,15 @@ public void Validate_FromB2CAuthority_WithTfpIssuer_ThrowsException() ValidIssuers = new[] { ValidatorConstants.B2CIssuerTfp }, })); + ValidationResult validatedIssuer = await ValidateIssuerAsync( + ValidatorConstants.B2CIssuerTfp, + ValidatorConstants.B2CIssuerTfp, + jwtSecurityToken, + validator); + + Assert.False(validatedIssuer.IsValid); + IdentityComparer.AreEqual(typeof(SecurityTokenInvalidIssuerException).ToString(), validatedIssuer.Error.ExceptionType.ToString(), context); + IdentityComparer.AreEqual(LogMessages.IDX40002, validatedIssuer.Error.MessageDetail.Message, context); IdentityComparer.AreEqual(LogMessages.IDX40002, exception.Message, context); TestUtilities.AssertFailIfErrors(context); } @@ -603,7 +744,7 @@ public void Validate_FromB2CAuthority_WithTfpIssuer_ThrowsException() [InlineData(ProtocolVersion.V2, ProtocolVersion.V1)] [InlineData(ProtocolVersion.V2, ProtocolVersion.V11)] [InlineData(ProtocolVersion.V2, ProtocolVersion.V2)] - public void Validate_WithAuthorityUsingConfigurationProvider(ProtocolVersion authorityVersion, ProtocolVersion tokenVersion) + public async Task Validate_WithAuthorityUsingConfigurationProvider(ProtocolVersion authorityVersion, ProtocolVersion tokenVersion) { var configurationManagerProvider = (string authority) => { @@ -671,7 +812,13 @@ public void Validate_WithAuthorityUsingConfigurationProvider(ProtocolVersion aut var aadIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(authority, _httpClient, configurationManagerProvider); var actualIssuer = aadIssuerValidator.Validate(tokenIssuer, jwtSecurityToken, new TokenValidationParameters()); + ValidationResult validatedIssuer = await ValidateIssuerAsync( + tokenIssuer, + jwtSecurityToken, + aadIssuerValidator); + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(tokenIssuer, validatedIssuer.Result.Issuer, context); IdentityComparer.AreEqual(tokenIssuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); } @@ -686,7 +833,7 @@ public void Validate_WithAuthorityUsingConfigurationProvider(ProtocolVersion aut [InlineData(ProtocolVersion.V2, ProtocolVersion.V1)] [InlineData(ProtocolVersion.V2, ProtocolVersion.V11)] [InlineData(ProtocolVersion.V2, ProtocolVersion.V2)] - public void Validate_UsesLKGWithoutConfigurationProvider(ProtocolVersion authorityVersion, ProtocolVersion tokenVersion) + public async Task Validate_UsesLKGWithoutConfigurationProvider(ProtocolVersion authorityVersion, ProtocolVersion tokenVersion) { var tokenIssuerProvider = (ProtocolVersion version) => { @@ -772,6 +919,13 @@ public void Validate_UsesLKGWithoutConfigurationProvider(ProtocolVersion authori // set LKG var actualIssuer = aadIssuerValidator.Validate(issuer, jwtSecurityToken, new TokenValidationParameters()); + ValidationResult validatedIssuer = await ValidateIssuerAsync( + issuer, + jwtSecurityToken, + aadIssuerValidator); + + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(issuer, validatedIssuer.Result.Issuer, context); IdentityComparer.AreEqual(issuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); @@ -779,6 +933,14 @@ public void Validate_UsesLKGWithoutConfigurationProvider(ProtocolVersion authori configurationManagerSetter(aadIssuerValidator, true); actualIssuer = aadIssuerValidator.Validate(issuer, jwtSecurityToken, new TokenValidationParameters { ValidateWithLKG = true }); + validatedIssuer = await ValidateIssuerAsync( + issuer, + jwtSecurityToken, + aadIssuerValidator, + true); + + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(issuer, validatedIssuer.Result.Issuer, context); IdentityComparer.AreEqual(issuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); } @@ -793,7 +955,7 @@ public void Validate_UsesLKGWithoutConfigurationProvider(ProtocolVersion authori [InlineData(ProtocolVersion.V2, ProtocolVersion.V1)] [InlineData(ProtocolVersion.V2, ProtocolVersion.V11)] [InlineData(ProtocolVersion.V2, ProtocolVersion.V2)] - public void Validate_CanFetchMetadataWithoutConfigurationProvider(ProtocolVersion authorityVersion, ProtocolVersion tokenVersion) + public async Task Validate_CanFetchMetadataWithoutConfigurationProvider(ProtocolVersion authorityVersion, ProtocolVersion tokenVersion) { var tokenIssuerProvider = (ProtocolVersion version) => { @@ -825,16 +987,19 @@ public void Validate_CanFetchMetadataWithoutConfigurationProvider(ProtocolVersio var jwtSecurityToken = new JwtSecurityToken(issuer: issuer, claims: new[] { issClaim, tidClaim }); var authority = authorityUrlProvider(authorityVersion); - var aadIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(authority, _httpClient); + var validator = AadIssuerValidator.GetAadIssuerValidator(authority, _httpClient); - // set LKG - var actualIssuer = aadIssuerValidator.Validate(issuer, jwtSecurityToken, new TokenValidationParameters()); + ValidationResult validatedIssuer = await ValidateIssuerAsync(issuer, jwtSecurityToken, validator); + var actualIssuer = validator.Validate(issuer, jwtSecurityToken, new TokenValidationParameters()); + + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(issuer, validatedIssuer.Result.Issuer, context); IdentityComparer.AreEqual(issuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); } [Fact] - public void Validate_UsesLKGWithConfigurationProvider() + public async Task Validate_UsesLKGWithConfigurationProvider() { var v1Configuration = new OpenIdConnectConfiguration { @@ -890,7 +1055,13 @@ public void Validate_UsesLKGWithConfigurationProvider() // set LKG var actualIssuer = aadIssuerValidator.Validate(v2TokenIssuer, jwtSecurityToken, new TokenValidationParameters()); + ValidationResult validatedIssuer = await ValidateIssuerAsync( + v2TokenIssuer, + jwtSecurityToken, + aadIssuerValidator); + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(v2TokenIssuer, validatedIssuer.Result.Issuer, context); IdentityComparer.AreEqual(v2TokenIssuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); @@ -899,6 +1070,14 @@ public void Validate_UsesLKGWithConfigurationProvider() v2ConfigurationManager.RequestRefresh(); actualIssuer = aadIssuerValidator.Validate(v2TokenIssuer, jwtSecurityToken, new TokenValidationParameters { ValidateWithLKG = true }); + validatedIssuer = await ValidateIssuerAsync( + v2TokenIssuer, + jwtSecurityToken, + aadIssuerValidator, + true); + + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(v2TokenIssuer, validatedIssuer.Result.Issuer, context); IdentityComparer.AreEqual(v2TokenIssuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); @@ -908,9 +1087,19 @@ public void Validate_UsesLKGWithConfigurationProvider() // before testing v1 LKG setup v1 LKG for v2 manager for cross version validation _ = aadIssuerValidator.Validate(v1TokenIssuer, v1JwtSecurityToken, new TokenValidationParameters()); + _ = await ValidateIssuerAsync( + v1TokenIssuer, + v1JwtSecurityToken, + aadIssuerValidator); // V1 token and authority behaves like v2 token and authority actualIssuer = v1AadIssuerValidator.Validate(v1TokenIssuer, v1JwtSecurityToken, new TokenValidationParameters()); + validatedIssuer = await ValidateIssuerAsync( + v1TokenIssuer, + v1JwtSecurityToken, + aadIssuerValidator); + + IdentityComparer.AreEqual(validatedIssuer.Result.Issuer, v1TokenIssuer, context); IdentityComparer.AreEqual(v1TokenIssuer, actualIssuer, context); IdentityComparer.AreEqual(null, v1ConfigurationManager.LastKnownGoodConfiguration, context); TestUtilities.AssertFailIfErrors(context); @@ -920,17 +1109,32 @@ public void Validate_UsesLKGWithConfigurationProvider() v1ConfigurationManager.RequestRefresh(); actualIssuer = v1AadIssuerValidator.Validate(v1TokenIssuer, v1JwtSecurityToken, new TokenValidationParameters { ValidateWithLKG = true }); + validatedIssuer = await ValidateIssuerAsync( + v1TokenIssuer, + v1JwtSecurityToken, + aadIssuerValidator, + true); + + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(v1TokenIssuer, validatedIssuer.Result.Issuer, context); IdentityComparer.AreEqual(v1TokenIssuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); // validating cross versions also validates with LKG actualIssuer = aadIssuerValidator.Validate(v1TokenIssuer, v1JwtSecurityToken, new TokenValidationParameters { ValidateWithLKG = true }); - + validatedIssuer = await ValidateIssuerAsync( + v1TokenIssuer, + v1JwtSecurityToken, + aadIssuerValidator, + true); + + Assert.True(validatedIssuer.IsValid); + IdentityComparer.AreEqual(v1TokenIssuer, validatedIssuer.Result.Issuer, context); IdentityComparer.AreEqual(v1TokenIssuer, actualIssuer, context); TestUtilities.AssertFailIfErrors(context); // if LKG not valid validation fails - // set confgimanager lkg lifetime to 1ms + // set ConfigurationManager lkg lifetime to 1ms // validate successfully to set LKG // wait 1ms, validate with expired LKG v1ConfigurationManager.RefreshedConfiguration = v1Configuration; @@ -938,12 +1142,23 @@ public void Validate_UsesLKGWithConfigurationProvider() v1ConfigurationManager.LastKnownGoodLifetime = TimeSpan.FromMilliseconds(1); actualIssuer = aadIssuerValidator.Validate(v1TokenIssuer, v1JwtSecurityToken, new TokenValidationParameters()); + validatedIssuer = await ValidateIssuerAsync( + v1TokenIssuer, + v1JwtSecurityToken, + aadIssuerValidator); + Thread.Sleep(TimeSpan.FromMilliseconds(1)); var securityExceptionThrown = false; var exceptionMessage = string.Empty; try { + validatedIssuer = await ValidateIssuerAsync( + v1TokenIssuer, + v1JwtSecurityToken, + aadIssuerValidator, + true); + _ = aadIssuerValidator.Validate(v1TokenIssuer, v1JwtSecurityToken, new TokenValidationParameters { ValidateWithLKG = true }); } catch (SecurityTokenInvalidIssuerException securityException) @@ -952,11 +1167,65 @@ public void Validate_UsesLKGWithConfigurationProvider() exceptionMessage = securityException.Message; } + Assert.False(validatedIssuer.IsValid); + IdentityComparer.AreEqual(string.Format(LogMessages.IDX40001, "https://sts.windows.net/f645ad92-e38d-4d1a-b510-d1b09a74a8ca/"), validatedIssuer.Error.MessageDetail.Message, context); + IdentityComparer.AreEqual(true, securityExceptionThrown, context); - IdentityComparer.AreEqual("IDX40001: Issuer: 'https://sts.windows.net/f645ad92-e38d-4d1a-b510-d1b09a74a8ca/', does not match any of the valid issuers provided for this application. ", exceptionMessage, context); + IdentityComparer.AreEqual(string.Format(LogMessages.IDX40001, "https://sts.windows.net/f645ad92-e38d-4d1a-b510-d1b09a74a8ca/"), exceptionMessage, context); TestUtilities.AssertFailIfErrors(context); } + + private static async Task> ValidateIssuerAsync( + string issuerFromToken, + SecurityToken securityToken, + AadIssuerValidator validator, + bool validateWithLKG = false) + { + ValidationParameters validationParameters = new ValidationParameters() { ValidateWithLKG = validateWithLKG }; + CallContext callContext = new CallContext(); + + return await validator.ValidateIssuerAsync( + issuerFromToken, + securityToken, + validationParameters, + callContext, + CancellationToken.None); + } + + private static async Task> ValidateIssuerAsync( + string issuerFromToken, + string addIssuerToValidationParameters, + SecurityToken securityToken, + AadIssuerValidator validator) + { + ValidationParameters validationParameters = new ValidationParameters(); + validationParameters.ValidIssuers.Add(addIssuerToValidationParameters); + CallContext callContext = new CallContext(); + + return await validator.ValidateIssuerAsync( + issuerFromToken, + securityToken, + validationParameters, + callContext, + CancellationToken.None); + } + + private static async Task> ValidateIssuerAsync( + string issuerFromToken, + BaseConfigurationManager configurationManager, + SecurityToken securityToken, + AadIssuerValidator validator) + { + ValidationParameters validationParameters = new ValidationParameters(); + validationParameters.ConfigurationManager = configurationManager; + + return await validator.ValidateIssuerAsync( + issuerFromToken, + securityToken, + validationParameters, + new CallContext(), + CancellationToken.None); + } } } - #pragma warning restore CS3016 // Arrays as attribute arguments is not CLS-compliant