diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt index ac84a36e84..7f8e906007 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.JsonWebTokens/InternalAPI.Unshipped.txt @@ -1,4 +1,6 @@ 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> Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.JsonWebToken(string header, string payload, Microsoft.IdentityModel.Tokens.ReadTokenPayloadValueDelegate readTokenPayloadValueDelegate) -> void Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.JsonWebToken(string jwtEncodedString, Microsoft.IdentityModel.Tokens.ReadTokenPayloadValueDelegate readTokenPayloadValueDelegate) -> void Microsoft.IdentityModel.JsonWebTokens.JsonWebToken.JsonWebToken(System.ReadOnlyMemory encodedTokenMemory, Microsoft.IdentityModel.Tokens.ReadTokenPayloadValueDelegate readTokenPayloadValueDelegate) -> void @@ -12,4 +14,3 @@ static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.WriteJweHeader( static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.WriteJweHeader(Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor tokenDescriptor) -> byte[] static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.WriteJwsHeader(ref System.Text.Json.Utf8JsonWriter writer, Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor tokenDescriptor) -> void static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.WriteJwsHeader(ref System.Text.Json.Utf8JsonWriter writer, Microsoft.IdentityModel.Tokens.SigningCredentials signingCredentials, Microsoft.IdentityModel.Tokens.EncryptingCredentials encryptingCredentials, System.Collections.Generic.IDictionary jweHeaderClaims, System.Collections.Generic.IDictionary jwsHeaderClaims, string tokenType, bool includeKeyIdInHeader) -> void -static Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.StackFrames.IssuerValidatorThrew -> System.Diagnostics.StackFrame diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.DecryptToken.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.DecryptToken.cs index 7b7a641cb2..4ca9467379 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.DecryptToken.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.DecryptToken.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Text; using Microsoft.IdentityModel.Logging; @@ -31,49 +30,42 @@ internal ValidationResult DecryptToken( { if (jwtToken == null) { - StackFrame tokenNullStackFrame = StackFrames.DecryptionTokenNull ??= new StackFrame(true); return ValidationError.NullParameter( nameof(jwtToken), - tokenNullStackFrame); + ValidationError.GetCurrentStackFrame()); } if (validationParameters == null) { - StackFrame validationParametersNullStackFrame = StackFrames.DecryptionValidationParametersNull ??= new StackFrame(true); return ValidationError.NullParameter( nameof(validationParameters), - validationParametersNullStackFrame); + ValidationError.GetCurrentStackFrame()); } if (string.IsNullOrEmpty(jwtToken.Enc)) { - StackFrame headerMissingStackFrame = StackFrames.DecryptionHeaderMissing ??= new StackFrame(true); return new ValidationError( new MessageDetail(TokenLogMessages.IDX10612), ValidationFailureType.TokenDecryptionFailed, typeof(SecurityTokenException), - headerMissingStackFrame); + ValidationError.GetCurrentStackFrame()); } (IList? contentEncryptionKeys, ValidationError? validationError) result = GetContentEncryptionKeys(jwtToken, validationParameters, configuration, callContext); if (result.validationError != null) - { - StackFrame decryptionGetKeysStackFrame = StackFrames.DecryptionGetEncryptionKeys ??= new StackFrame(true); - return result.validationError.AddStackFrame(decryptionGetKeysStackFrame); - } + return result.validationError.AddCurrentStackFrame(); if (result.contentEncryptionKeys == null || result.contentEncryptionKeys.Count == 0) { - StackFrame noKeysTriedStackFrame = StackFrames.DecryptionNoKeysTried ??= new StackFrame(true); return new ValidationError( new MessageDetail( TokenLogMessages.IDX10609, LogHelper.MarkAsSecurityArtifact(jwtToken, JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.TokenDecryptionFailed, typeof(SecurityTokenDecryptionFailedException), - noKeysTriedStackFrame); + ValidationError.GetCurrentStackFrame()); } return JwtTokenUtilities.DecryptJwtToken( @@ -211,7 +203,6 @@ internal ValidationResult DecryptToken( return (unwrappedKeys, null); else { - StackFrame decryptionKeyUnwrapFailedStackFrame = StackFrames.DecryptionKeyUnwrapFailed ??= new StackFrame(true); ValidationError validationError = new( new MessageDetail( TokenLogMessages.IDX10618, @@ -220,7 +211,7 @@ internal ValidationResult DecryptToken( LogHelper.MarkAsSecurityArtifact(jwtToken, JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.TokenDecryptionFailed, typeof(SecurityTokenKeyWrapException), - decryptionKeyUnwrapFailedStackFrame); + ValidationError.GetCurrentStackFrame()); return (null, validationError); } diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ReadToken.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ReadToken.cs index 44929e628b..8ea160e10a 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ReadToken.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ReadToken.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Diagnostics; using Microsoft.IdentityModel.Tokens; #nullable enable @@ -28,10 +27,9 @@ internal static ValidationResult ReadToken( { if (string.IsNullOrEmpty(token)) { - StackFrame nullTokenStackFrame = StackFrames.ReadTokenNullOrEmpty ?? new StackFrame(true); return ValidationError.NullParameter( nameof(token), - nullTokenStackFrame); + ValidationError.GetCurrentStackFrame()); } try @@ -43,12 +41,11 @@ internal static ValidationResult ReadToken( catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types { - StackFrame malformedTokenStackFrame = StackFrames.ReadTokenMalformed ?? new StackFrame(true); return new ValidationError( new MessageDetail(LogMessages.IDX14107), ValidationFailureType.TokenReadingFailed, typeof(SecurityTokenMalformedException), - malformedTokenStackFrame, + ValidationError.GetCurrentStackFrame(), ex); } } diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs index 1bb3e578ca..8d9811531e 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -18,47 +17,35 @@ public partial class JsonWebTokenHandler : TokenHandler { /// /// Validates a token. - /// On a validation failure, no exception will be thrown; instead, the exception will be set in the returned TokenValidationResult.Exception property. - /// Callers should always check the TokenValidationResult.IsValid property to verify the validity of the result. + /// On validation failure no exception will be thrown. 'see cref="ValidationError"' will contain information pertaining to the error. /// /// The token to be validated. /// The to be used for validating the token. - /// A that contains useful information for logging. + /// 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. - /// - /// ValidationError.GetException() will return one of the following exceptions if the is invalid. - /// - /// Returned if is null or empty. - /// Returned if is null. - /// Returned if 'token.Length' is greater than . - /// Returned if is not a valid , - /// Returned if the validationParameters.TokenReader delegate is not able to parse/read the token as a valid , - internal async Task> ValidateTokenAsync( + internal override async Task> ValidateTokenAsync( string token, ValidationParameters validationParameters, CallContext callContext, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(token)) { - StackFrame nullTokenStackFrame = StackFrames.TokenStringNull ??= new StackFrame(true); return ValidationError.NullParameter( nameof(token), - nullTokenStackFrame); + ValidationError.GetCurrentStackFrame()); } if (validationParameters is null) { - StackFrame nullValidationParametersStackFrame = StackFrames.TokenStringValidationParametersNull ??= new StackFrame(true); return ValidationError.NullParameter( nameof(validationParameters), - nullValidationParametersStackFrame); + ValidationError.GetCurrentStackFrame()); } if (token.Length > MaximumTokenSizeInBytes) { - StackFrame invalidTokenLengthStackFrame = StackFrames.InvalidTokenLength ??= new StackFrame(true); return new ValidationError( new MessageDetail( TokenLogMessages.IDX10209, @@ -66,7 +53,7 @@ internal async Task> ValidateTokenAsync( LogHelper.MarkAsNonPII(MaximumTokenSizeInBytes)), ValidationFailureType.InvalidSecurityToken, typeof(ArgumentException), - invalidTokenLengthStackFrame); + ValidationError.GetCurrentStackFrame()); } ValidationResult readResult = ReadToken(token, callContext); @@ -82,45 +69,40 @@ internal async Task> ValidateTokenAsync( if (validationResult.IsValid) return validationResult; // No need to unwrap and re-wrap the result. - StackFrame validationFailureStackFrame = StackFrames.TokenStringValidationFailed ??= new StackFrame(true); - return validationResult.UnwrapError().AddStackFrame(validationFailureStackFrame); + return validationResult.UnwrapError().AddCurrentStackFrame(); } - StackFrame readFailureStackFrame = StackFrames.TokenStringReadFailed ??= new StackFrame(true); - return readResult.UnwrapError().AddStackFrame(readFailureStackFrame); + return readResult.UnwrapError().AddCurrentStackFrame(); } /// - internal async Task> ValidateTokenAsync( + internal override async Task> ValidateTokenAsync( SecurityToken token, ValidationParameters validationParameters, CallContext callContext, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) { if (token is null) { - StackFrame nullTokenStackFrame = StackFrames.TokenNull ??= new StackFrame(true); return ValidationError.NullParameter( nameof(token), - nullTokenStackFrame); + ValidationError.GetCurrentStackFrame()); } if (validationParameters is null) { - StackFrame nullValidationParametersStackFrame = StackFrames.TokenValidationParametersNull ??= new StackFrame(true); return ValidationError.NullParameter( nameof(validationParameters), - nullValidationParametersStackFrame); + ValidationError.GetCurrentStackFrame()); } if (token is not JsonWebToken jsonWebToken) { - StackFrame notJwtStackFrame = StackFrames.TokenNotJWT ??= new StackFrame(true); return new ValidationError( new MessageDetail(TokenLogMessages.IDX10001, nameof(token), nameof(JsonWebToken)), ValidationFailureType.InvalidSecurityToken, typeof(ArgumentException), - notJwtStackFrame); + ValidationError.GetCurrentStackFrame()); } BaseConfiguration? currentConfiguration = @@ -200,8 +182,7 @@ await ValidateJWEAsync(jsonWebToken, validationParameters, lkgConfiguration, cal } // If we reach this point, the token validation failed and we should return the error. - StackFrame stackFrame = StackFrames.TokenValidationFailed ??= new StackFrame(true); - return result.UnwrapError().AddStackFrame(stackFrame); + return result.UnwrapError().AddCurrentStackFrame(); } private async ValueTask> ValidateJWEAsync( @@ -215,15 +196,13 @@ private async ValueTask> ValidateJWEAsync( jwtToken, validationParameters, configuration, callContext); if (!decryptionResult.IsValid) { - StackFrame decryptionFailureStackFrame = StackFrames.DecryptionFailed ??= new StackFrame(true); - return decryptionResult.UnwrapError().AddStackFrame(decryptionFailureStackFrame); + return decryptionResult.UnwrapError().AddCurrentStackFrame(); } ValidationResult readResult = ReadToken(decryptionResult.UnwrapResult(), callContext); if (!readResult.IsValid) { - StackFrame readFailureStackFrame = StackFrames.DecryptedReadFailed ??= new StackFrame(true); - return readResult.UnwrapError().AddStackFrame(readFailureStackFrame); + return readResult.UnwrapError().AddCurrentStackFrame(); } JsonWebToken decryptedToken = (readResult.UnwrapResult() as JsonWebToken)!; @@ -233,8 +212,7 @@ await ValidateJWSAsync(decryptedToken!, validationParameters, configuration, cal if (!validationResult.IsValid) { - StackFrame validationFailureStackFrame = StackFrames.JWEValidationFailed ??= new StackFrame(true); - return validationResult.UnwrapError().AddStackFrame(validationFailureStackFrame); + return validationResult.UnwrapError().AddCurrentStackFrame(); } JsonWebToken jsonWebToken = (validationResult.UnwrapResult().SecurityToken as JsonWebToken)!; @@ -289,10 +267,7 @@ private async ValueTask> ValidateJWSAsync( tokenAudiences, jsonWebToken, validationParameters, callContext); if (!audienceValidationResult.IsValid) - { - StackFrame audienceValidationFailureStackFrame = StackFrames.AudienceValidationFailed ??= new StackFrame(true); - return audienceValidationResult.UnwrapError().AddStackFrame(audienceValidationFailureStackFrame); - } + return audienceValidationResult.UnwrapError().AddCurrentStackFrame(); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) @@ -362,10 +337,12 @@ private async ValueTask> ValidateJWSAsync( { ValidationResult actorReadingResult = ReadToken(jsonWebToken.Actor, callContext); if (!actorReadingResult.IsValid) - { - StackFrame actorReadingFailureStackFrame = StackFrames.ActorReadFailed ??= new StackFrame(true); - return actorReadingResult.UnwrapError().AddStackFrame(actorReadingFailureStackFrame); - } + return actorReadingResult.UnwrapError().AddCurrentStackFrame(); + + if (validationParameters.ActorValidationParameters is null) + return ValidationError.NullParameter( + nameof(validationParameters.ActorValidationParameters), + ValidationError.GetCurrentStackFrame()); JsonWebToken actorToken = (actorReadingResult.UnwrapResult() as JsonWebToken)!; ValidationParameters actorParameters = validationParameters.ActorValidationParameters; @@ -374,10 +351,7 @@ await ValidateJWSAsync(actorToken, actorParameters, configuration, callContext, .ConfigureAwait(false); if (!innerActorValidationResult.IsValid) - { - StackFrame actorValidationFailureStackFrame = StackFrames.ActorValidationFailed ??= new StackFrame(true); - return innerActorValidationResult.UnwrapError().AddStackFrame(actorValidationFailureStackFrame); - } + return innerActorValidationResult.UnwrapError().AddCurrentStackFrame(); actorValidationResult = innerActorValidationResult; } @@ -410,17 +384,14 @@ await ValidateJWSAsync(actorToken, actorParameters, configuration, callContext, ValidationResult signatureValidationResult = ValidateSignature( jsonWebToken, validationParameters, configuration, callContext); if (!signatureValidationResult.IsValid) - { - StackFrame signatureValidationFailureStackFrame = StackFrames.SignatureValidationFailed ??= new StackFrame(true); - return signatureValidationResult.UnwrapError().AddStackFrame(signatureValidationFailureStackFrame); - } + return signatureValidationResult.UnwrapError().AddCurrentStackFrame(); ValidationResult issuerSigningKeyValidationResult; 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.JsonWebTokens/JsonWebTokenHandler.ValidateToken.StackFrames.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.StackFrames.cs deleted file mode 100644 index 69a02ccacb..0000000000 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.StackFrames.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Diagnostics; -using Microsoft.IdentityModel.Tokens; - -#nullable enable -namespace Microsoft.IdentityModel.JsonWebTokens -{ - public partial class JsonWebTokenHandler : TokenHandler - { - // Cached stack frames to build exceptions from validation errors - internal static class StackFrames - { - // ValidateTokenAsync from string - internal static StackFrame? TokenStringNull; - internal static StackFrame? TokenStringValidationParametersNull; - internal static StackFrame? InvalidTokenLength; - internal static StackFrame? TokenStringValidationFailed; - internal static StackFrame? TokenStringReadFailed; - // ValidateTokenAsync from SecurityToken - internal static StackFrame? TokenNull; - internal static StackFrame? TokenValidationParametersNull; - internal static StackFrame? TokenNotJWT; - internal static StackFrame? TokenValidationFailed; - // ValidateJWEAsync - internal static StackFrame? DecryptionFailed; - internal static StackFrame? DecryptedReadFailed; - internal static StackFrame? JWEValidationFailed; - // ValidateJWSAsync - internal static StackFrame? LifetimeValidationFailed; - internal static StackFrame? AudienceValidationFailed; - internal static StackFrame? IssuerValidationFailed; - internal static StackFrame? ReplayValidationFailed; - internal static StackFrame? ActorReadFailed; - internal static StackFrame? ActorValidationFailed; - internal static StackFrame? TypeValidationFailed; - internal static StackFrame? SignatureValidationFailed; - internal static StackFrame? IssuerSigningKeyValidationFailed; - // DecryptToken - internal static StackFrame? DecryptionTokenNull; - internal static StackFrame? DecryptionValidationParametersNull; - internal static StackFrame? DecryptionHeaderMissing; - internal static StackFrame? DecryptionGetEncryptionKeys; - internal static StackFrame? DecryptionNoKeysTried; - internal static StackFrame? DecryptionKeyUnwrapFailed; - // ReadToken - internal static StackFrame? ReadTokenNullOrEmpty; - internal static StackFrame? ReadTokenMalformed; - // ValidateSignature - internal static StackFrame? KidNotMatchedNoTryAll; - internal static StackFrame? NoKeysProvided; - } - } -} -#nullable restore diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JwtTokenUtilities.DecryptTokenResult.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JwtTokenUtilities.DecryptTokenResult.cs index a4a242cd90..82a416b751 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JwtTokenUtilities.DecryptTokenResult.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JwtTokenUtilities.DecryptTokenResult.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Diagnostics; using System.Text; using Microsoft.IdentityModel.Tokens; using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages; @@ -28,12 +27,12 @@ internal static ValidationResult DecryptJwtToken( if (validationParameters == null) return ValidationError.NullParameter( nameof(validationParameters), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); if (decryptionParameters == null) return ValidationError.NullParameter( nameof(decryptionParameters), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); bool decryptionSucceeded = false; bool algorithmNotSupportedByCryptoProvider = false; @@ -124,7 +123,7 @@ internal static ValidationResult DecryptJwtToken( new MessageDetail(TokenLogMessages.IDX10679, zipAlgorithm), ValidationFailureType.TokenDecryptionFailed, typeof(SecurityTokenDecompressionFailedException), - new StackFrame(true), + ValidationError.GetCurrentStackFrame(), ex); } } diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JwtTokenUtilities.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JwtTokenUtilities.cs index 2794ff2d8d..02eb40bce4 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JwtTokenUtilities.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JwtTokenUtilities.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics; using System.Globalization; using System.Security.Claims; using System.Security.Cryptography; @@ -373,7 +372,7 @@ private static ValidationError GetDecryptionError( LogHelper.MarkAsSecurityArtifact(decryptionParameters.EncodedToken, SafeLogJwtToken)), ValidationFailureType.TokenDecryptionFailed, typeof(SecurityTokenDecryptionFailedException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); else if (algorithmNotSupportedByCryptoProvider) return new ValidationError( new MessageDetail( @@ -382,7 +381,7 @@ private static ValidationError GetDecryptionError( LogHelper.MarkAsNonPII(decryptionParameters.Enc)), ValidationFailureType.TokenDecryptionFailed, typeof(SecurityTokenDecryptionFailedException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); else return new ValidationError( new MessageDetail( @@ -390,7 +389,7 @@ private static ValidationError GetDecryptionError( LogHelper.MarkAsSecurityArtifact(decryptionParameters.EncodedToken, SafeLogJwtToken)), ValidationFailureType.TokenDecryptionFailed, typeof(SecurityTokenDecryptionFailedException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); } private static byte[] DecryptToken(CryptoProviderFactory cryptoProviderFactory, SecurityKey key, string encAlg, byte[] ciphertext, byte[] headerAscii, byte[] initializationVector, byte[] authenticationTag) diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs index b5e6bf0eba..6d1fb7a5a2 100644 --- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs +++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs @@ -248,22 +248,41 @@ public virtual async Task GetConfigurationAsync(CancellationToken cancel) { if (_refreshRequested) { - // Log as manual because RequestRefresh was called - TelemetryClient.IncrementConfigurationRefreshRequestCounter( - MetadataAddress, - TelemetryConstants.Protocols.Manual); + _refreshRequested = false; + + try + { + // Log as manual because RequestRefresh was called + TelemetryClient.IncrementConfigurationRefreshRequestCounter( + MetadataAddress, + TelemetryConstants.Protocols.Manual); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch + { } +#pragma warning restore CA1031 // Do not catch general exception types UpdateCurrentConfiguration(); - _refreshRequested = false; } - else + else if (SyncAfter <= _timeProvider.GetUtcNow()) { - TelemetryClient.IncrementConfigurationRefreshRequestCounter( - MetadataAddress, - TelemetryConstants.Protocols.Automatic); + try + { + TelemetryClient.IncrementConfigurationRefreshRequestCounter( + MetadataAddress, + TelemetryConstants.Protocols.Automatic); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch + { } +#pragma warning restore CA1031 // Do not catch general exception types _ = Task.Run(UpdateCurrentConfiguration, CancellationToken.None); } + else + { + Interlocked.Exchange(ref _configurationRetrieverState, ConfigurationRetrieverIdle); + } } } diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt index 55944f8e39..80105438bd 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt @@ -1,6 +1,5 @@ const Microsoft.IdentityModel.Tokens.Saml.LogMessages.IDX11402 = "IDX11402: Unable to read SamlSecurityToken. Exception thrown: '{0}'." -> string const Microsoft.IdentityModel.Tokens.Saml2.LogMessages.IDX13003 = "IDX13003: Unable to read Saml2SecurityToken. Exception thrown: '{0}'." -> string -Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.CreateClaimsIdentity(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, string issuer) -> System.Security.Claims.ClaimsIdentity Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedAudience.get -> string @@ -9,44 +8,23 @@ Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedConditions(string ValidatedAudience, Microsoft.IdentityModel.Tokens.ValidatedLifetime? ValidatedLifetime) -> void Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedLifetime.get -> Microsoft.IdentityModel.Tokens.ValidatedLifetime? Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedLifetime.set -> void -Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateTokenAsync(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.Tokens.Saml.SamlSecurityTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.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.Saml.SamlSecurityTokenHandler.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.Saml.SamlValidationError Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.SamlValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException = null) -> void 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.StackFrames -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) -> System.Threading.Tasks.Task> +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.GetException() -> 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.Saml2.Saml2ValidationError.GetException() -> System.Exception -static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.IssuerValidationFailed -> System.Diagnostics.StackFrame +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.SamlSecurityTokenHandler.StackFrames.AssertionConditionsNull -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.AssertionConditionsValidationFailed -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.AssertionNull -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.AudienceValidationFailed -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.LifetimeValidationFailed -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.OneTimeUseValidationFailed -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.TokenNull -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.TokenValidationParametersNull -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.Saml.SamlTokenUtilities.ResolveTokenSigningKey(Microsoft.IdentityModel.Xml.KeyInfo tokenKeyInfo, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters) -> Microsoft.IdentityModel.Tokens.SecurityKey -static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.AssertionConditionsNull -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.AssertionConditionsValidationFailed -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.AssertionNull -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.AudienceValidationFailed -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.IssuerSigningKeyValidationFailed -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.IssuerValidationFailed -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.LifetimeValidationFailed -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.OneTimeUseValidationFailed -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.SignatureValidationFailed -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.TokenNull -> System.Diagnostics.StackFrame -static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.TokenValidationParametersNull -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateSignature(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult virtual Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ProcessStatements(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, string issuer, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters) -> System.Collections.Generic.IEnumerable virtual Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ReadSamlToken(string token, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/Exceptions/SamlValidationError.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/Exceptions/SamlValidationError.cs index d7f39f8cc4..8883c110ce 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/Exceptions/SamlValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/Exceptions/SamlValidationError.cs @@ -7,9 +7,20 @@ #nullable enable namespace Microsoft.IdentityModel.Tokens.Saml { + /// + /// Represents a SAML validation error. + /// internal class SamlValidationError : ValidationError { - internal SamlValidationError( + /// + /// Initializes a new instance of the class. + /// + /// contains information about the exception that is used to generate the exception message. + /// is the type of validation failure that occurred. + /// is the type of exception that occurred. + /// is the stack frame where the exception occurred. + /// is the inner exception that occurred. + public SamlValidationError( MessageDetail messageDetail, ValidationFailureType validationFailureType, Type exceptionType, @@ -19,7 +30,11 @@ internal SamlValidationError( { } - internal override Exception GetException() + /// + /// Creates an instance of an using + /// + /// An instance of an Exception. + protected override Exception CreateException() { if (ExceptionType == typeof(SamlSecurityTokenReadException)) { @@ -27,7 +42,7 @@ internal override Exception GetException() return exception; } - return base.GetException(); + return base.CreateException(); } } } 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 2e073084f8..752c41aba1 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -15,11 +14,21 @@ namespace Microsoft.IdentityModel.Tokens.Saml /// public partial class SamlSecurityTokenHandler : SecurityTokenHandler { - internal async Task> ValidateTokenAsync( + /// + /// Validates a token. + /// On a validation failure, no exception will be thrown; instead, the will contain the information about the error that occurred. + /// Callers should always check the ValidationResult.IsValid property to verify the validity of the result. + /// + /// The token to be validated. + /// The to be used for validating the token. + /// 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 override Task> ValidateTokenAsync( string token, ValidationParameters validationParameters, CallContext callContext, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) { if (token is null) return ValidationError.NullParameter(nameof(token), ValidationError.GetCurrentStackFrame()); @@ -34,20 +43,17 @@ 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, -#pragma warning disable CA1801 // Review unused parameters - CancellationToken cancellationToken) -#pragma warning restore CA1801 // Review unused parameters + CancellationToken cancellationToken = default) { if (securityToken is null) { - StackFrames.TokenNull ??= new StackFrame(true); return ValidationError.NullParameter( nameof(securityToken), - StackFrames.TokenNull); + ValidationError.GetCurrentStackFrame()); } if (securityToken is not SamlSecurityToken samlToken) @@ -65,10 +71,9 @@ internal async Task> ValidateTokenAsync( if (validationParameters is null) { - StackFrames.TokenValidationParametersNull ??= new StackFrame(true); return ValidationError.NullParameter( nameof(validationParameters), - StackFrames.TokenValidationParametersNull); + ValidationError.GetCurrentStackFrame()); } ValidationResult conditionsResult = ValidateConditions(samlToken, validationParameters, callContext); @@ -135,10 +140,7 @@ internal async Task> ValidateTokenAsync( ValidationResult signatureValidationResult = ValidateSignature(samlToken, validationParameters, callContext); if (!signatureValidationResult.IsValid) - { - StackFrames.SignatureValidationFailed ??= new StackFrame(true); - return signatureValidationResult.UnwrapError().AddStackFrame(StackFrames.SignatureValidationFailed); - } + return signatureValidationResult.UnwrapError().AddCurrentStackFrame(); ValidationResult issuerSigningKeyValidationResult; @@ -148,7 +150,6 @@ internal async Task> ValidateTokenAsync( samlToken.SigningKey, samlToken, validationParameters, - null, callContext); if (!issuerSigningKeyValidationResult.IsValid) @@ -188,18 +189,16 @@ internal virtual ValidationResult ValidateConditions( { if (samlToken.Assertion is null) { - StackFrames.AssertionNull ??= new StackFrame(true); return ValidationError.NullParameter( nameof(samlToken.Assertion), - StackFrames.AssertionNull); + ValidationError.GetCurrentStackFrame()); } if (samlToken.Assertion.Conditions is null) { - StackFrames.AssertionConditionsNull ??= new StackFrame(true); return ValidationError.NullParameter( nameof(samlToken.Assertion.Conditions), - StackFrames.AssertionConditionsNull); + ValidationError.GetCurrentStackFrame()); } ValidationResult lifetimeValidationResult; diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.StackFrames.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.StackFrames.cs deleted file mode 100644 index c7a05acfc0..0000000000 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.StackFrames.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Diagnostics; - -#nullable enable -namespace Microsoft.IdentityModel.Tokens.Saml -{ - public partial class SamlSecurityTokenHandler : SecurityTokenHandler - { - // Cached stack frames to build exceptions from validation errors - internal static class StackFrames - { - // Stack frames from ValidateTokenAsync using SecurityToken - internal static StackFrame? TokenNull; - internal static StackFrame? TokenValidationParametersNull; - internal static StackFrame? IssuerValidationFailed; - internal static StackFrame? SignatureValidationFailed; - - // Stack frames from ValidateConditions - internal static StackFrame? AudienceValidationFailed; - internal static StackFrame? AssertionNull; - internal static StackFrame? AssertionConditionsNull; - internal static StackFrame? AssertionConditionsValidationFailed; - internal static StackFrame? LifetimeValidationFailed; - internal static StackFrame? OneTimeUseValidationFailed; - } - } -} -#nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Exceptions/Saml2ValidationError.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Exceptions/Saml2ValidationError.cs index 7425f50679..260f562096 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Exceptions/Saml2ValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Exceptions/Saml2ValidationError.cs @@ -7,9 +7,20 @@ #nullable enable namespace Microsoft.IdentityModel.Tokens.Saml2 { + /// + /// Represents a SAML2 validation error. + /// internal class Saml2ValidationError : ValidationError { - internal Saml2ValidationError( + /// + /// Initializes a new instance of the class. + /// + /// contains information about the exception that is used to generate the exception message. + /// is the type of validation failure that occurred. + /// is the type of exception that occurred. + /// is the stack frame where the exception occurred. + /// is the inner exception that occurred. + public Saml2ValidationError( MessageDetail messageDetail, ValidationFailureType validationFailureType, Type exceptionType, @@ -19,7 +30,11 @@ internal Saml2ValidationError( { } - internal override Exception GetException() + /// + /// Creates an instance of an using + /// + /// An instance of an Exception. + protected override Exception CreateException() { if (ExceptionType == typeof(Saml2SecurityTokenReadException)) { @@ -27,7 +42,7 @@ internal override Exception GetException() return exception; } - return base.GetException(); + return base.CreateException(); } } } 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 4b9f18d590..497e62d29a 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.IdentityModel.Tokens.Saml; @@ -16,11 +15,21 @@ namespace Microsoft.IdentityModel.Tokens.Saml2 /// public partial class Saml2SecurityTokenHandler : SecurityTokenHandler { - internal async Task> ValidateTokenAsync( + /// + /// Validates a token. + /// On a validation failure, no exception will be thrown; instead, the will contain the information about the error that occurred. + /// Callers should always check the ValidationResult.IsValid property to verify the validity of the result. + /// + /// The token to be validated. + /// The to be used for validating the token. + /// 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 override async Task> ValidateTokenAsync( string token, ValidationParameters validationParameters, CallContext callContext, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) { if (token is null) return ValidationError.NullParameter(nameof(token), ValidationError.GetCurrentStackFrame()); @@ -35,18 +44,17 @@ 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, - CancellationToken cancellationToken) + CancellationToken cancellationToken = default) { if (securityToken is null) { - StackFrames.TokenNull ??= new StackFrame(true); return ValidationError.NullParameter( nameof(securityToken), - StackFrames.TokenNull); + ValidationError.GetCurrentStackFrame()); } if (securityToken is not Saml2SecurityToken samlToken) @@ -64,10 +72,9 @@ internal async Task> ValidateTokenAsync( if (validationParameters is null) { - StackFrames.TokenValidationParametersNull ??= new StackFrame(true); return ValidationError.NullParameter( nameof(validationParameters), - StackFrames.TokenValidationParametersNull); + ValidationError.GetCurrentStackFrame()); } validationParameters = await SamlTokenUtilities.PopulateValidationParametersWithCurrentConfigurationAsync(validationParameters, cancellationToken).ConfigureAwait(false); @@ -138,10 +145,7 @@ internal async Task> ValidateTokenAsync( var signatureValidationResult = ValidateSignature(samlToken, validationParameters, callContext); if (!signatureValidationResult.IsValid) - { - StackFrames.SignatureValidationFailed ??= new StackFrame(true); - return signatureValidationResult.UnwrapError().AddStackFrame(StackFrames.SignatureValidationFailed); - } + return signatureValidationResult.UnwrapError().AddCurrentStackFrame(); ValidationResult issuerSigningKeyValidationResult; @@ -151,7 +155,6 @@ internal async Task> ValidateTokenAsync( samlToken.SigningKey, samlToken, validationParameters, - null, callContext); if (!issuerSigningKeyValidationResult.IsValid) @@ -191,18 +194,16 @@ internal virtual ValidationResult ValidateConditions( { if (samlToken.Assertion is null) { - StackFrames.AssertionNull ??= new StackFrame(true); return ValidationError.NullParameter( nameof(samlToken.Assertion), - StackFrames.AssertionNull); + ValidationError.GetCurrentStackFrame()); } if (samlToken.Assertion.Conditions is null) { - StackFrames.AssertionConditionsNull ??= new StackFrame(true); return ValidationError.NullParameter( nameof(samlToken.Assertion.Conditions), - StackFrames.AssertionConditionsNull); + ValidationError.GetCurrentStackFrame()); } ValidationResult lifetimeValidationResult; diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.StackFrames.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.StackFrames.cs deleted file mode 100644 index 42c3ee2832..0000000000 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.StackFrames.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Diagnostics; - -#nullable enable -namespace Microsoft.IdentityModel.Tokens.Saml2 -{ - public partial class Saml2SecurityTokenHandler : SecurityTokenHandler - { - // Cached stack frames to build exceptions from validation errors - internal static class StackFrames - { - // Stack frames from ValidateTokenAsync using SecurityToken - internal static StackFrame? TokenNull; - internal static StackFrame? TokenValidationParametersNull; - internal static StackFrame? IssuerSigningKeyValidationFailed; - internal static StackFrame? IssuerValidationFailed; - internal static StackFrame? SignatureValidationFailed; - - // Stack frames from ValidateConditions - internal static StackFrame? AudienceValidationFailed; - internal static StackFrame? AssertionNull; - internal static StackFrame? AssertionConditionsNull; - internal static StackFrame? AssertionConditionsValidationFailed; - internal static StackFrame? LifetimeValidationFailed; - internal static StackFrame? OneTimeUseValidationFailed; - } - } -} -#nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt index 58044612f2..8ce71ead77 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -65,8 +65,8 @@ Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.InvalidSigningKey Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.IssuerSigningKeyValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, Microsoft.IdentityModel.Tokens.SecurityKey invalidSigningKey, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.IssuerValidationError.InvalidIssuer.get -> string Microsoft.IdentityModel.Tokens.IssuerValidationError.IssuerValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidIssuer, System.Exception innerException = null) -> void -Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedConfiguration = 1 -> Microsoft.IdentityModel.Tokens.IssuerValidationSource -Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedValidationParameters = 2 -> Microsoft.IdentityModel.Tokens.IssuerValidationSource +Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerValidationSource(string name) -> void +Microsoft.IdentityModel.Tokens.IssuerValidationSource.Name.get -> string Microsoft.IdentityModel.Tokens.LifetimeValidationError.Expires.get -> System.DateTime? Microsoft.IdentityModel.Tokens.LifetimeValidationError.LifetimeValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.DateTime? notBefore, System.DateTime? expires, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.LifetimeValidationError.NotBefore.get -> System.DateTime? @@ -104,6 +104,16 @@ Microsoft.IdentityModel.Tokens.TokenTypeValidationError.TokenTypeValidationError Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.get -> System.TimeProvider Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.set -> void Microsoft.IdentityModel.Tokens.ValidatedToken.Log(Microsoft.Extensions.Logging.ILogger logger) -> void +Microsoft.IdentityModel.Tokens.IssuerValidationSource +Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerValidationSource(string name) -> void +Microsoft.IdentityModel.Tokens.IssuerValidationSource.Name.get -> string +Microsoft.IdentityModel.Tokens.ValidatedIssuer +Microsoft.IdentityModel.Tokens.ValidatedIssuer.Issuer.get -> string +Microsoft.IdentityModel.Tokens.ValidatedIssuer.Issuer.init -> void +Microsoft.IdentityModel.Tokens.ValidatedIssuer.ValidatedIssuer() -> void +Microsoft.IdentityModel.Tokens.ValidatedIssuer.ValidatedIssuer(string Issuer, Microsoft.IdentityModel.Tokens.IssuerValidationSource ValidationSource) -> void +Microsoft.IdentityModel.Tokens.ValidatedIssuer.ValidationSource.get -> Microsoft.IdentityModel.Tokens.IssuerValidationSource +Microsoft.IdentityModel.Tokens.ValidatedIssuer.ValidationSource.init -> void Microsoft.IdentityModel.Tokens.ValidationError.AddCurrentStackFrame(string filePath = "", int lineNumber = 0, int skipFrames = 1) -> Microsoft.IdentityModel.Tokens.ValidationError Microsoft.IdentityModel.Tokens.ValidationError.GetException(System.Type exceptionType, System.Exception innerException) -> System.Exception Microsoft.IdentityModel.Tokens.ValidationError.Log(Microsoft.Extensions.Logging.ILogger logger) -> void @@ -132,6 +142,9 @@ static Microsoft.IdentityModel.Tokens.Utility.SerializeAsSingleCommaDelimitedStr static Microsoft.IdentityModel.Tokens.ValidationError.GetCurrentStackFrame(string filePath = "", int lineNumber = 0, int skipFrames = 1) -> System.Diagnostics.StackFrame static readonly Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.ConfigurationManagerCounter -> System.Diagnostics.Metrics.Counter static readonly Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.TotalDurationHistogram -> System.Diagnostics.Metrics.Histogram +static readonly Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedConfiguration -> Microsoft.IdentityModel.Tokens.IssuerValidationSource +static readonly Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedValidationParameters -> Microsoft.IdentityModel.Tokens.IssuerValidationSource +static readonly Microsoft.IdentityModel.Tokens.IssuerValidationSource.NotValidated -> Microsoft.IdentityModel.Tokens.IssuerValidationSource static readonly Microsoft.IdentityModel.Tokens.LoggingEventId.TokenValidationFailed -> Microsoft.Extensions.Logging.EventId static readonly Microsoft.IdentityModel.Tokens.LoggingEventId.TokenValidationSucceeded -> Microsoft.Extensions.Logging.EventId static readonly Microsoft.IdentityModel.Telemetry.TelemetryDataRecorder.ConfigurationManagerCounter -> System.Diagnostics.Metrics.Counter @@ -150,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/Results/Details/AlgorithmValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AlgorithmValidationError.cs index de0dd174d5..d07e05a3fd 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AlgorithmValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AlgorithmValidationError.cs @@ -7,8 +7,21 @@ #nullable enable namespace Microsoft.IdentityModel.Tokens { + /// + /// Represents a validation error that occurs when a token's algorithm cannot be validated. + /// If available, the invalid algorithm is stored in . + /// internal class AlgorithmValidationError : ValidationError { + /// + /// Initializes a new instance of the class. + /// + /// contains information about the exception that is used to generate the exception message. + /// is the type of validation failure that occurred. + /// is the type of exception that occurred. + /// is the stack frame where the exception occurred. + /// is the algorithm that could not be validated. Can be null if the algorithm is missing from the token. + /// if present, represents the exception that occurred during validation. public AlgorithmValidationError( MessageDetail messageDetail, ValidationFailureType validationFailureType, @@ -21,7 +34,11 @@ public AlgorithmValidationError( InvalidAlgorithm = invalidAlgorithm; } - internal override Exception GetException() + /// + /// Creates an instance of an using + /// + /// An instance of an Exception. + protected override Exception CreateException() { if (ExceptionType == typeof(SecurityTokenInvalidAlgorithmException)) { @@ -34,10 +51,13 @@ internal override Exception GetException() return exception; } - return base.GetException(); + return base.CreateException(); } - protected string? InvalidAlgorithm { get; } + /// + /// The algorithm that could not be validated. + /// + public string? InvalidAlgorithm { get; } } } #nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AudienceValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AudienceValidationError.cs index 4c6dbd42c3..0dd9ab9515 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AudienceValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AudienceValidationError.cs @@ -8,8 +8,23 @@ #nullable enable namespace Microsoft.IdentityModel.Tokens { + /// + /// Represents an error that occurs when the token's audience cannot be validated. + /// If available, the invalid audiences from the token are stored in + /// and the allowed audiences are stored in . + /// internal class AudienceValidationError : ValidationError { + /// + /// Initializes a new instance of the class. + /// + /// contains information about the exception that is used to generate the exception message. + /// is the type of validation failure that occurred. + /// is the type of exception that occurred. + /// is the stack frame where the exception occurred. + /// are the audiences that were in the token. Can be null if no audiences were found in the token. + /// are the audiences that were expected. Can be null if no valid audiences were provided in the validation parameters. + /// if present, represents the exception that occurred during validation. public AudienceValidationError( MessageDetail messageDetail, ValidationFailureType validationFailureType, @@ -28,7 +43,7 @@ public AudienceValidationError( /// Creates an instance of an using /// /// An instance of an Exception. - internal override Exception GetException() + protected override Exception CreateException() { if (ExceptionType == typeof(SecurityTokenInvalidAudienceException)) { @@ -38,11 +53,18 @@ internal override Exception GetException() return exception; } - return base.GetException(ExceptionType, null); + return base.CreateException(ExceptionType, null); } - protected IList? TokenAudiences { get; } - protected IList? ValidAudiences { get; } + /// + /// The audiences that were in the token. + /// + public IList? TokenAudiences { get; } + + /// + /// The audiences that were expected. + /// + public IList? ValidAudiences { get; } } } #nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/IssuerSigningKeyValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/IssuerSigningKeyValidationError.cs index 8f380847ae..2251c43eb0 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/IssuerSigningKeyValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/IssuerSigningKeyValidationError.cs @@ -7,9 +7,22 @@ #nullable enable namespace Microsoft.IdentityModel.Tokens { + /// + /// Represents a validation error that occurs when the issuer signing key cannot be validated. + /// If available, the invalid signing key is stored in . + /// internal class IssuerSigningKeyValidationError : ValidationError { - internal IssuerSigningKeyValidationError( + /// + /// Initializes a new instance of the class. + /// + /// contains information about the exception that is used to generate the exception message. + /// is the type of validation failure that occurred. + /// is the type of exception that occurred. + /// is the stack frame where the exception occurred. + /// is the signing key that could not be validated. Can be null if the signing key for the token is missing. + /// if present, represents the exception that occurred during validation. + public IssuerSigningKeyValidationError( MessageDetail messageDetail, ValidationFailureType validationFailureType, Type exceptionType, @@ -21,7 +34,11 @@ internal IssuerSigningKeyValidationError( InvalidSigningKey = invalidSigningKey; } - internal override Exception GetException() + /// + /// Creates an instance of an using + /// + /// An instance of an Exception. + protected override Exception CreateException() { if (ExceptionType == typeof(SecurityTokenInvalidSigningKeyException)) { @@ -34,16 +51,25 @@ internal override Exception GetException() return exception; } - return base.GetException(); + return base.CreateException(); } - internal static new IssuerSigningKeyValidationError NullParameter(string parameterName, StackFrame stackFrame) => new( + /// + /// Creates a new instance of representing a null parameter. + /// + /// The name of the parameter. + /// The stack frame where the error occurred. + /// A new . + public static new IssuerSigningKeyValidationError NullParameter(string parameterName, StackFrame stackFrame) => new( MessageDetail.NullParameter(parameterName), ValidationFailureType.NullArgument, typeof(SecurityTokenArgumentNullException), stackFrame, null); // InvalidSigningKey + /// + /// The signing key that was found invalid. + /// protected SecurityKey? InvalidSigningKey { get; } } } diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/IssuerValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/IssuerValidationError.cs index 438681b973..8b384c763a 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/IssuerValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/IssuerValidationError.cs @@ -7,9 +7,22 @@ #nullable enable namespace Microsoft.IdentityModel.Tokens { + /// + /// Represents an error that occurs when the issuer of a token cannot be validated. + /// If available, the invalid issuer is stored in . + /// internal class IssuerValidationError : ValidationError { - internal IssuerValidationError( + /// + /// Initializes a new instance of the class. + /// + /// contains information about the exception that is used to generate the exception message. + /// is the type of validation failure that occurred. + /// is the type of exception that occurred. + /// is the stack frame where the exception occurred. + /// is the issuer that could not be validated. Can be null if the issuer is missing from the token. + /// if present, represents the exception that occurred during validation. + public IssuerValidationError( MessageDetail messageDetail, ValidationFailureType validationFailureType, Type exceptionType, @@ -21,9 +34,16 @@ internal IssuerValidationError( InvalidIssuer = invalidIssuer; } - internal string? InvalidIssuer { get; } + /// + /// The issuer that could not be validated. + /// + public string? InvalidIssuer { get; } - internal override Exception GetException() + /// + /// Creates an instance of an using + /// + /// An instance of an exception. + protected override Exception CreateException() { if (ExceptionType == typeof(SecurityTokenInvalidIssuerException)) { @@ -36,7 +56,7 @@ internal override Exception GetException() return exception; } - return base.GetException(); + return base.CreateException(); } } } diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/LifetimeValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/LifetimeValidationError.cs index f193985d81..38762280ad 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/LifetimeValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/LifetimeValidationError.cs @@ -7,8 +7,22 @@ #nullable enable namespace Microsoft.IdentityModel.Tokens { + /// + /// Represents an error that occurs when a token's lifetime cannot be validated. + /// If available, the not before and expires values are stored in and . + /// internal class LifetimeValidationError : ValidationError { + /// + /// Initializes a new instance of the class. + /// + /// contains information about the exception that is used to generate the exception message. + /// is the type of validation failure that occurred. + /// is the type of exception that occurred. + /// is the stack frame where the exception occurred. + /// is the date from which the token is valid. Can be null if the token does not contain a not before claim. + /// is the date at which the token expires. Can be null if the token does not contain an expires claim. + /// if present, represents the exception that occurred during validation. public LifetimeValidationError( MessageDetail messageDetail, ValidationFailureType validationFailureType, @@ -28,7 +42,7 @@ public LifetimeValidationError( /// Creates an instance of an using /// /// An instance of an Exception. - internal override Exception GetException() + protected override Exception CreateException() { if (ExceptionType == typeof(SecurityTokenNoExpirationException)) { @@ -65,12 +79,18 @@ internal override Exception GetException() return exception; } else - return base.GetException(ExceptionType, null); + return base.CreateException(ExceptionType, null); } - protected DateTime? NotBefore { get; } + /// + /// The date from which the token is valid. + /// + public DateTime? NotBefore { get; } - protected DateTime? Expires { get; } + /// + /// The date at which the token expires. + /// + public DateTime? Expires { get; } } } #nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/MessageDetail.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/MessageDetail.cs index 188e3a51b8..b0b71fd296 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/MessageDetail.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/MessageDetail.cs @@ -26,6 +26,11 @@ public MessageDetail(string formatString, params object[] parameters) Parameters = parameters; } + /// + /// Creates a message detail for a null parameter. + /// + /// The name of the parameter. + /// A new . public static MessageDetail NullParameter(string parameterName) => new MessageDetail(LogMessages.IDX10000, LogHelper.MarkAsNonPII(parameterName)); diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/SignatureValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/SignatureValidationError.cs index 78c51069d8..9d024a074f 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/SignatureValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/SignatureValidationError.cs @@ -7,8 +7,20 @@ #nullable enable namespace Microsoft.IdentityModel.Tokens { + /// + /// Represents an error that occurs when the token's signature cannot be validated. + /// internal class SignatureValidationError : ValidationError { + /// + /// Initializes a new instance of the class. + /// + /// contains information about the exception that is used to generate the exception message. + /// is the type of validation failure that occurred. + /// is the type of exception that occurred. + /// is the stack frame where the exception occurred. + /// if present, is the inner validation error that caused this signature validation error. + /// if present, represents the exception that occurred during validation. public SignatureValidationError( MessageDetail messageDetail, ValidationFailureType validationFailureType, @@ -21,7 +33,11 @@ public SignatureValidationError( InnerValidationError = innerValidationError; } - internal override Exception GetException() + /// + /// Creates an instance of an using + /// + /// An instance of an exception. + protected override Exception CreateException() { var inner = InnerException ?? InnerValidationError?.GetException(); @@ -40,10 +56,16 @@ internal override Exception GetException() return exception; } - return base.GetException(); + return base.CreateException(); } - internal static new SignatureValidationError NullParameter( + /// + /// Creates a new instance of representing a null parameter. + /// + /// The name of the parameter. + /// The stack frame where the error occurred. + /// A new . + public static new SignatureValidationError NullParameter( string parameterName, StackFrame stackFrame) => new( MessageDetail.NullParameter(parameterName), ValidationFailureType.NullArgument, @@ -51,7 +73,10 @@ internal override Exception GetException() stackFrame, null); // innerValidationError - protected internal ValidationError? InnerValidationError { get; } + /// + /// The inner validation error that caused this signature validation error. + /// + public ValidationError? InnerValidationError { get; } } } #nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenReplayValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenReplayValidationError.cs index 02d2c53d4a..125df5e6a7 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenReplayValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenReplayValidationError.cs @@ -7,9 +7,22 @@ #nullable enable namespace Microsoft.IdentityModel.Tokens { + /// + /// Represents an error that occurs when a token cannot be validated against being re-used or replay is detected. + /// If available, the expiration time of the token that failed the validation is included. + /// internal class TokenReplayValidationError : ValidationError { - internal TokenReplayValidationError( + /// + /// Initializes a new instance of the class. + /// + /// contains information about the exception that is used to generate the exception message. + /// is the type of validation failure that occurred. + /// is the type of exception that occurred. + /// is the stack frame where the exception occurred. + /// is the expiration time of the token that failed the validation. Can be null if the token does not have an expiration time. + /// if present, represents the exception that occurred during validation. + public TokenReplayValidationError( MessageDetail messageDetail, ValidationFailureType validationFailureType, Type exceptionType, @@ -21,7 +34,11 @@ internal TokenReplayValidationError( ExpirationTime = expirationTime; } - internal override Exception GetException() + /// + /// Creates an instance of an using + /// + /// An instance of an exception. + protected override Exception CreateException() { if (ExceptionType == typeof(SecurityTokenReplayDetectedException)) { @@ -38,17 +55,26 @@ internal override Exception GetException() return exception; } - return base.GetException(); + return base.CreateException(); } - internal static new TokenReplayValidationError NullParameter(string parameterName, StackFrame stackFrame) => new( + /// + /// Creates a new instance of representing a null parameter. + /// + /// The name of the parameter. + /// The stack frame where the error occurred. + /// A new . + public static new TokenReplayValidationError NullParameter(string parameterName, StackFrame stackFrame) => new( MessageDetail.NullParameter(parameterName), ValidationFailureType.NullArgument, typeof(SecurityTokenArgumentNullException), stackFrame, null); - protected DateTime? ExpirationTime { get; } + /// + /// The expiration time of the token that failed the validation. + /// + public DateTime? ExpirationTime { get; } } } #nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenTypeValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenTypeValidationError.cs index 298abeefce..1dd7d5f68a 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenTypeValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenTypeValidationError.cs @@ -7,9 +7,22 @@ #nullable enable namespace Microsoft.IdentityModel.Tokens { + /// + /// Represents an error that occurs when a token type cannot be validated. + /// If available, the invalid token type is stored in . + /// internal class TokenTypeValidationError : ValidationError { - internal TokenTypeValidationError( + /// + /// Initializes a new instance of the class. + /// + /// contains information about the exception that is used to generate the exception message. + /// is the type of validation failure that occurred. + /// is the type of exception that occurred. + /// is the stack frame where the exception occurred. + /// is the token type that could not be validated. Can be null if the token type is missing from the token. + /// if present, represents the exception that occurred during validation. + public TokenTypeValidationError( MessageDetail messageDetail, ValidationFailureType validationFailureType, Type exceptionType, @@ -21,7 +34,11 @@ internal TokenTypeValidationError( InvalidTokenType = invalidTokenType; } - internal override Exception GetException() + /// + /// Creates an instance of an using + /// + /// An instance of an exception. + protected override Exception CreateException() { if (ExceptionType == typeof(SecurityTokenInvalidTypeException)) { @@ -34,17 +51,26 @@ internal override Exception GetException() return exception; } - return base.GetException(); + return base.CreateException(); } - internal static new TokenTypeValidationError NullParameter(string parameterName, StackFrame stackFrame) => new( + /// + /// Creates a new instance of representing a null parameter. + /// + /// The name of the parameter. + /// The stack frame where the error occurred. + /// A new . + public static new TokenTypeValidationError NullParameter(string parameterName, StackFrame stackFrame) => new( MessageDetail.NullParameter(parameterName), ValidationFailureType.NullArgument, typeof(SecurityTokenArgumentNullException), stackFrame, null); // invalidTokenType - protected string? InvalidTokenType { get; } + /// + /// The token type that could not be validated. + /// + public string? InvalidTokenType { get; } } } #nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs index b4e0a6714f..2405a1e802 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs @@ -13,12 +13,15 @@ namespace Microsoft.IdentityModel.Tokens { /// - /// Contains information so that Exceptions can be logged or thrown written as required. + /// Represents an error that occurred during token validation. + /// If necessary, it can be used to create an instance of . /// internal class ValidationError { private Type _exceptionType; + private Exception? _exception; + /// /// Creates an instance of /// @@ -26,8 +29,8 @@ internal class ValidationError /// is the type of validation failure that occurred. /// is the type of exception that occurred. /// is the stack frame where the exception occurred. - /// is the inner exception that occurred. - internal ValidationError( + /// if present, represents the exception that occurred during validation. + internal protected ValidationError( MessageDetail messageDetail, ValidationFailureType validationFailureType, Type exceptionType, @@ -44,16 +47,24 @@ internal ValidationError( }; } + public Exception GetException() + { + if (_exception is null) + _exception = CreateException(); + + return _exception; + } + /// /// Creates an instance of an using /// /// An instance of an Exception. - internal virtual Exception GetException() + protected virtual Exception CreateException() { - return GetException(ExceptionType, InnerException); + return CreateException(ExceptionType, InnerException); } - internal Exception GetException(Type exceptionType, Exception? innerException) + internal Exception CreateException(Type exceptionType, Exception? innerException) { Exception? exception = null; @@ -184,12 +195,22 @@ internal Exception GetException(Type exceptionType, Exception? innerException) return exception; } - internal void Log(ILogger logger) + /// + /// Logs the validation error. + /// + /// The to be used for logging. + public void Log(ILogger logger) { Logger.TokenValidationFailed(logger, FailureType.Name, MessageDetail.Message); } - internal static ValidationError NullParameter(string parameterName, StackFrame stackFrame) => new( + /// + /// Creates a new instance of representing a null parameter. + /// + /// The name of the parameter. + /// The stack frame where the error occurred. + /// A new . + public static ValidationError NullParameter(string parameterName, StackFrame stackFrame) => new( MessageDetail.NullParameter(parameterName), ValidationFailureType.NullArgument, typeof(SecurityTokenArgumentNullException), @@ -211,6 +232,11 @@ internal void Log(ILogger logger) /// public Exception? InnerException { get; } + /// + /// Gets the message that explains the error. + /// + public string Message => MessageDetail.Message; + /// /// Gets the message details that are used to generate the exception message. /// @@ -240,7 +266,7 @@ public ValidationError AddStackFrame(StackFrame stackFrame) /// The line number from which this method is called. CAptured automatically by default. /// The number of stack frames to skip when capturing. Used to avoid capturing this method and get the caller instead. /// The updated object. - internal ValidationError AddCurrentStackFrame([CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, int skipFrames = 1) + public ValidationError AddCurrentStackFrame([CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, int skipFrames = 1) { // We add 1 to the skipped frames to skip the current method StackFrames.Add(GetCurrentStackFrame(filePath, lineNumber, skipFrames + 1)); @@ -256,7 +282,7 @@ internal ValidationError AddCurrentStackFrame([CallerFilePath] string filePath = /// The number of stack frames to skip when capturing. Used to avoid capturing this method and get the caller instead. /// The captured stack frame. /// If this is called from a helper method, consider adding an extra skip frame to avoid capturing the helper instead. - internal static StackFrame GetCurrentStackFrame( + public static StackFrame GetCurrentStackFrame( [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0, int skipFrames = 1) { // String is allocated, but it goes out of scope immediately after the call @@ -286,7 +312,13 @@ private static class Logger public static void TokenValidationFailed( ILogger logger, string validationFailureType, - string messageDetail) => s_tokenValidationFailed(logger, validationFailureType, messageDetail, null); + string messageDetail) + { + if (logger.IsEnabled(LogLevel.Information)) + { + s_tokenValidationFailed(logger, validationFailureType, messageDetail, null); + } + } } } } diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/IssuerValidationSource.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/IssuerValidationSource.cs new file mode 100644 index 0000000000..7d066dd15a --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/IssuerValidationSource.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#nullable enable +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Represents the source from which the token issuer was validated. + /// i.e. whether the issuer was matched with the configuration provided or the validation parameters provided. + /// If a custom issuer validation delegate is used, a custom validation source can be instantiated and used. + /// + internal class IssuerValidationSource + { + /// + /// Initializes a new instance of . + /// + /// The name of the issuer validation source. + public IssuerValidationSource(string name) => Name = name; + + /// + /// The name of the issuer validation source. + /// + public string Name { get; } + + /// + /// Represents the issuer validation source that has not been validated. + /// + public static readonly IssuerValidationSource NotValidated = new("NotValidated"); + + /// + /// Represents the issuer validation source that has been matched with the configuration provided. + /// + public static readonly IssuerValidationSource IssuerMatchedConfiguration = new("IssuerMatchedConfiguration"); + + /// + /// Represents the issuer validation source that has been matched with the validation parameters provided. + /// + public static readonly IssuerValidationSource IssuerMatchedValidationParameters = new("IssuerMatchedValidationParameters"); + + /// + /// The issuer validation source's string representation. + /// + /// The name of the issuer validation source. + public override string ToString() => Name; + } +} +#nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedIssuer.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedIssuer.cs new file mode 100644 index 0000000000..d797da269a --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedIssuer.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Represents a validated issuer, including the source of the validation. + /// + internal readonly struct ValidatedIssuer : IEquatable + { + /// + /// Initializes a new instance of . + /// + /// The issuer that was validated. + /// The source of the validation, i.e. configuration, validation parameters. + public ValidatedIssuer(string issuer, IssuerValidationSource validationSource) + { + Issuer = issuer; + ValidationSource = validationSource; + } + + /// + /// The issuer that was validated. + /// + public string Issuer { get; } + + /// + /// The source of the validation, i.e. configuration, validation parameters. + /// + public IssuerValidationSource ValidationSource { get; } + + /// + /// Determines whether the specified object is equal to the current instance of . + /// + /// The object to compare with the current instance. + /// true if the specified object is equal to the current instance; otherwise, false. + public override bool Equals(object? obj) + { + if (obj is ValidatedIssuer other) + { + return Equals(other); + } + + return false; + } + + /// + /// Returns the hash code for this instance of . + /// + /// + public override int GetHashCode() + { + return Issuer.GetHashCode() ^ ValidationSource.GetHashCode(); + } + + /// + /// Equality comparison operator for . + /// + /// The left value to compare. + /// The right value to compare. + /// A boolean indicating whether the left value is equal to the right one. + public static bool operator ==(ValidatedIssuer left, ValidatedIssuer right) + { + return left.Equals(right); + } + + /// + /// Inequality comparison operator for . + /// + /// The left value to compare. + /// The right value to compare. + /// A boolean indicating whether the left value is not equal to the right one. + public static bool operator !=(ValidatedIssuer left, ValidatedIssuer right) + { + return !(left == right); + } + + /// + /// Determines whether the specified is equal to the current instance. + /// + /// The to compare with the current instance. + /// true if the specified is equal to the current instance; otherwise, false. + public bool Equals(ValidatedIssuer other) + { + if (other.Issuer != Issuer || other.ValidationSource != ValidationSource) + { + return false; + } + + return true; + } + + /// + /// The validated issuer's string representation. + /// + /// A string representing the issuer and where it was validated from. + public override string ToString() => $"{Issuer} (from {ValidationSource})"; + } +} +#nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedLifetime.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedLifetime.cs new file mode 100644 index 0000000000..7ad05123a3 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedLifetime.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Represents a validated lifetime, including the NotBefore and Expires values. + /// + internal readonly struct ValidatedLifetime : IEquatable + { + /// + /// Initializes a new instance of . + /// + /// The representing the time from which the token is considered valid. + /// The representing the token's expiration time. + public ValidatedLifetime(DateTime? notBefore, DateTime? expires) + { + NotBefore = notBefore; + Expires = expires; + } + + /// + /// The representing the time from which the token is considered valid. + /// + public DateTime? NotBefore { get; } + + /// + /// The representing the token's expiration time. + /// + public DateTime? Expires { get; } + + /// + /// Determines whether the specified object is equal to the current instance of . + /// + /// The object to compare with the current instance. + /// true if the specified object is equal to the current instance; otherwise, false. + public override bool Equals(object? obj) + { + if (obj is ValidatedLifetime other) + { + return Equals(other); + } + + return false; + } + + /// + /// Returns the hash code for this instance of . + /// + /// + public override int GetHashCode() + { + return NotBefore.GetHashCode() ^ Expires.GetHashCode(); + } + + /// + /// Equality comparison operator for . + /// + /// The left value to compare. + /// The right value to compare. + /// A boolean indicating whether the left value is equal to the right one. + public static bool operator ==(ValidatedLifetime left, ValidatedLifetime right) + { + return left.Equals(right); + } + + /// + /// Inequality comparison operator for . + /// + /// The left value to compare. + /// The right value to compare. + /// A boolean indicating whether the left value is not equal to the right one. + public static bool operator !=(ValidatedLifetime left, ValidatedLifetime right) + { + return !(left == right); + } + + /// + /// Determines whether the specified is equal to the current instance. + /// + /// The to compare with the current instance. + /// true if the specified is equal to the current instance; otherwise, false. + public bool Equals(ValidatedLifetime other) + { + if (other.NotBefore != NotBefore || other.Expires != Expires) + { + return false; + } + + return true; + } + + /// + /// The validated lifetime's string representation. + /// + /// A string representing the validated lifetime. + public override string ToString() => $"[{NotBefore}, {Expires}]"; + } +} +#nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedSigningKeyLifetime.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedSigningKeyLifetime.cs new file mode 100644 index 0000000000..646ba63e97 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedSigningKeyLifetime.cs @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Represents a validated signing key lifetime. + /// + internal readonly struct ValidatedSigningKeyLifetime : IEquatable + { + /// + /// Initializes a new instance of . + /// + /// The date from which the signing key is considered valid. + /// The date until which the signing key is considered valid. + /// The time the validation occurred. + internal ValidatedSigningKeyLifetime(DateTime? validFrom, DateTime? validTo, DateTime? validationTime) + { + ValidFrom = validFrom; + ValidTo = validTo; + ValidationTime = validationTime; + } + + /// + /// The date from which the signing key is considered valid. + /// + public DateTime? ValidFrom { get; } + + /// + /// The date until which the signing key is considered valid. + /// + public DateTime? ValidTo { get; } + + /// + /// The time the validation occurred. + /// + public DateTime? ValidationTime { get; } + + /// + /// Determines whether the specified object is equal to the current instance of . + /// + /// The object to compare with the current instance. + /// true if the specified object is equal to the current instance; otherwise, false. + public override bool Equals(object? obj) + { + if (obj is ValidatedSigningKeyLifetime other) + { + return Equals(other); + } + + return false; + } + + /// + /// Returns the hash code for this instance of . + /// + /// + public override int GetHashCode() + { + return ValidFrom.GetHashCode() ^ ValidTo.GetHashCode() ^ ValidationTime.GetHashCode(); + } + + /// + /// Equality comparison operator for . + /// + /// The left value to compare. + /// The right value to compare. + /// A boolean indicating whether the left value is equal to the right one. + public static bool operator ==(ValidatedSigningKeyLifetime left, ValidatedSigningKeyLifetime right) + { + return left.Equals(right); + } + + /// + /// Inequality comparison operator for . + /// + /// The left value to compare. + /// The right value to compare. + /// A boolean indicating whether the left value is not equal to the right one. + public static bool operator !=(ValidatedSigningKeyLifetime left, ValidatedSigningKeyLifetime right) + { + return !(left == right); + } + + /// + /// Determines whether the specified is equal to the current instance. + /// + /// The to compare with the current instance. + /// true if the specified is equal to the current instance; otherwise, false. + public bool Equals(ValidatedSigningKeyLifetime other) + { + if (other.ValidFrom != ValidFrom || other.ValidTo != ValidTo || other.ValidationTime != ValidationTime) + { + return false; + } + + return true; + } + + /// + /// The validated signing key lifetime's string representation. + /// + /// A string that represents the validated signing key lifetime and the validation time. + public override string ToString() => $"{ValidationTime} ∊ [{ValidFrom}, {ValidTo}]"; + } +} +#nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedToken.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedToken.cs index 7eccd96c75..f2e87c33ef 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedToken.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedToken.cs @@ -17,27 +17,26 @@ namespace Microsoft.IdentityModel.Tokens internal class ValidatedToken { /// - /// Creates an instance of + /// Initializes a new instance of . /// - /// The that is being validated. - /// The that is being used to validate the token. - /// The to be used for validating the token. - internal ValidatedToken( + /// The that was validated. + /// The that was used to validate the token. + /// The used to validate the token. + /// If , , or is null. + public ValidatedToken( SecurityToken securityToken, TokenHandler tokenHandler, ValidationParameters validationParameters) { - TokenHandler = tokenHandler ?? throw new ArgumentNullException(nameof(tokenHandler)); SecurityToken = securityToken ?? throw new ArgumentNullException(nameof(securityToken)); + TokenHandler = tokenHandler ?? throw new ArgumentNullException(nameof(tokenHandler)); ValidationParameters = validationParameters ?? throw new ArgumentNullException(nameof(validationParameters)); } /// /// Logs the validation result. /// - public void Log(ILogger logger) - { - Logger.TokenValidationSucceeded( + public void Log(ILogger logger) => Logger.TokenValidationSucceeded( logger, ValidatedAudience ?? "none", ValidatedLifetime, @@ -46,29 +45,61 @@ public void Log(ILogger logger) ValidatedSigningKey?.KeyId ?? "none", ActorValidationResult is not null ); - } - public SecurityToken SecurityToken { get; private set; } + /// + /// The that was validated. + /// + public SecurityToken SecurityToken { get; } - public TokenHandler TokenHandler { get; private set; } + /// + /// The that was used to validate the token. + /// + public TokenHandler TokenHandler { get; } - public ValidationParameters ValidationParameters { get; private set; } + /// + /// The that were used to validate the token. + /// + public ValidationParameters ValidationParameters { get; } #region Validated Properties + /// + /// The result of validating the actor, if any. + /// public ValidatedToken? ActorValidationResult { get; internal set; } + /// + /// The audience that was validated, if any. + /// public string? ValidatedAudience { get; internal set; } + /// + /// The issuer that was validated. If present, it contains the source of the validation as well. + /// public ValidatedIssuer? ValidatedIssuer { get; internal set; } + /// + /// The lifetime that was validated, if any. + /// public ValidatedLifetime? ValidatedLifetime { get; internal set; } + /// + /// The expiration time of the token that was used to validate the token was not replayed, if any. + /// public DateTime? ValidatedTokenReplayExpirationTime { get; internal set; } + /// + /// The token type that was validated, if any. + /// public ValidatedTokenType? ValidatedTokenType { get; internal set; } + /// + /// The that was used to validate the token, if any. + /// public SecurityKey? ValidatedSigningKey { get; internal set; } + /// + /// The validated lifetime of the that was used to sign the token, if any. + /// public ValidatedSigningKeyLifetime? ValidatedSigningKeyLifetime { get; internal set; } #endregion @@ -177,6 +208,9 @@ private object ClaimsIdentitySyncObj #endregion #region Logging + /// + /// Internal class used for logging. + /// private static class Logger { private static readonly Action s_tokenValidationFailed = @@ -195,7 +229,13 @@ private static class Logger public static void TokenValidationFailed( ILogger logger, ValidationFailureType validationFailureType, - MessageDetail messageDetail) => s_tokenValidationFailed(logger, validationFailureType.Name, messageDetail.Message, null); + MessageDetail messageDetail) + { + if (logger.IsEnabled(LogLevel.Information)) + { + s_tokenValidationFailed(logger, validationFailureType.Name, messageDetail.Message, null); + } + } private static readonly Action s_tokenValidationSucceeded = LoggerMessage.Define( @@ -226,15 +266,21 @@ public static void TokenValidationSucceeded( ValidatedIssuer? validatedIssuer, ValidatedTokenType? validatedTokenType, string validatedSigningKeyId, - bool actorWasValidated) => s_tokenValidationSucceeded( - logger, - validatedAudience, - validatedLifetime, - validatedIssuer, - validatedTokenType, - validatedSigningKeyId, - actorWasValidated, - null); + bool actorWasValidated) + { + if (logger.IsEnabled(LogLevel.Debug)) + { + s_tokenValidationSucceeded( + logger, + validatedAudience, + validatedLifetime, + validatedIssuer, + validatedTokenType, + validatedSigningKeyId, + actorWasValidated, + null); + } + } } #endregion } diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedTokenType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedTokenType.cs new file mode 100644 index 0000000000..e94470c95e --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedTokenType.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Represents a validated token type, including the number of valid types present in the validation parameters. + /// + internal readonly struct ValidatedTokenType : IEquatable + { + /// + /// Initializes a new instance of . + /// + /// The token type that was validated. + /// The number of valid types present in the validation parameters. + public ValidatedTokenType(string type, int validTypeCount) + { + Type = type; + ValidTypeCount = validTypeCount; + } + + /// + /// The token type that was validated. + /// + public string Type { get; } + + /// + /// The number of valid types present in the validation parameters. + /// + public int ValidTypeCount { get; } + + /// + /// Determines whether the specified object is equal to the current instance of . + /// + /// The object to compare with the current instance. + /// true if the specified object is equal to the current instance; otherwise, false. + public override bool Equals(object? obj) + { + if (obj is ValidatedTokenType other) + { + return Equals(other); + } + + return false; + } + + /// + /// Returns the hash code for this instance of . + /// + /// The hash code for the current instance. + public override int GetHashCode() + { + return Type.GetHashCode() ^ ValidTypeCount.GetHashCode(); + } + + /// + /// Equality comparison operator for . + /// + /// The left value to compare. + /// The right value to compare. + /// A boolean indicating whether the left value is equal to the right one. + public static bool operator ==(ValidatedTokenType left, ValidatedTokenType right) + { + return left.Equals(right); + } + + /// + /// Inequality comparison operator for . + /// + /// The left value to compare. + /// The right value to compare. + /// A boolean indicating whether the left value is not equal to the right one. + public static bool operator !=(ValidatedTokenType left, ValidatedTokenType right) + { + return !(left == right); + } + + /// + /// Determines whether the specified is equal to the current instance. + /// + /// The to compare with the current instance. + /// true if the specified is equal to the current instance; otherwise, false. + public bool Equals(ValidatedTokenType other) + { + if (other.Type != Type || other.ValidTypeCount != ValidTypeCount) + return false; + + return true; + } + + /// + /// The validated token type's string representation. + /// + /// A string representing the validated token type and the amount of valid types. + public override string ToString() => $"{Type} ({ValidTypeCount} valid types)"; + } +} +#nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidationResult.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidationResult.cs index 1d5c475ba4..8bb3c9d5e0 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidationResult.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidationResult.cs @@ -17,7 +17,8 @@ namespace Microsoft.IdentityModel.Tokens readonly ValidationError? _error; /// - /// Creates a successful, valid validation result. + /// Creates a new instance of indicating a successful operation + /// and containing an object of the associated type. /// /// The value associated with the success. public ValidationResult(TResult result) @@ -28,7 +29,8 @@ public ValidationResult(TResult result) } /// - /// Creates an error, invalid validation result. + /// Creates a new instance of indicating a failed operation + /// and containing a with the error information. /// /// The error associated with the failure. public ValidationResult(ValidationError error) @@ -115,10 +117,10 @@ public TResult? Result } /// - /// + /// Determines whether the specified object is equal to the current instance of . /// - /// - /// + /// The object to compare with the current instance. + /// true if the specified object is equal to the current instance; otherwise, false. public override bool Equals(object? obj) { if (obj is ValidationResult other) @@ -130,10 +132,9 @@ public override bool Equals(object? obj) } /// - /// + /// Returns the hash code for this instance of . /// - /// - /// + /// The hash code for the current instance. public override int GetHashCode() { if (IsValid) @@ -143,32 +144,32 @@ public override int GetHashCode() } /// - /// + /// Equality comparison operator for . /// - /// - /// - /// + /// The left value to compare. + /// The right value to compare. + /// A boolean indicating whether the left value is equal to the right one. public static bool operator ==(ValidationResult left, ValidationResult right) { return left.Equals(right); } /// - /// + /// Inequality comparison operator for . /// - /// - /// - /// + /// The left value to compare. + /// The right value to compare. + /// A boolean indicating whether the left value is not equal to the right one. public static bool operator !=(ValidationResult left, ValidationResult right) { return !(left == right); } /// - /// + /// Determines whether the specified is equal to the current instance. /// - /// - /// + /// The to compare with the current instance. + /// true if the specified is equal to the current instance; otherwise, false. public bool Equals(ValidationResult other) { if (other.IsValid != IsValid) @@ -185,7 +186,7 @@ public bool Equals(ValidationResult other) /// # /// Required for compatibility, see CA2225 for more information /// The existing instance. - public ValidationResult ToResult() + public ValidationResult ToValidationResult() { return this; } diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs index 24fdaed4e5..864f974793 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs @@ -8,6 +8,7 @@ using System.Threading; using Microsoft.IdentityModel.Logging; +#nullable enable namespace Microsoft.IdentityModel.Tokens { /// @@ -15,21 +16,24 @@ namespace Microsoft.IdentityModel.Tokens /// internal class ValidationParameters { - private string _authenticationType; + private string? _authenticationType; private TimeSpan _clockSkew = DefaultClockSkew; private string _nameClaimType = ClaimsIdentity.DefaultNameClaimType; private string _roleClaimType = ClaimsIdentity.DefaultRoleClaimType; - private Dictionary _instancePropertyBag; - private IList _issuerSigningKeys; - private IList _validIssuers; - private IList _validTokenTypes; - private IList _validAudiences; + private Dictionary? _instancePropertyBag; + private IList? _issuerSigningKeys; + private Dictionary? _propertyBag; + private IList? _tokenDecryptionKeys; + private IList? _validIssuers; + private IList? _validTokenTypes; + private IList? _validAudiences; + private IList? _validAlgorithms; private AlgorithmValidationDelegate _algorithmValidator = Validators.ValidateAlgorithm; private AudienceValidationDelegate _audienceValidator = Validators.ValidateAudience; private IssuerValidationDelegateAsync _issuerValidatorAsync = Validators.ValidateIssuerAsync; private LifetimeValidationDelegate _lifetimeValidator = Validators.ValidateLifetime; - private SignatureValidationDelegate _signatureValidator; + private SignatureValidationDelegate? _signatureValidator; private TokenReplayValidationDelegate _tokenReplayValidator = Validators.ValidateTokenReplay; private TokenTypeValidationDelegate _tokenTypeValidator = Validators.ValidateTokenType; private IssuerSigningKeyValidationDelegate _issuerSigningKeyValidator = Validators.ValidateIssuerSigningKey; @@ -61,11 +65,13 @@ protected ValidationParameters(ValidationParameters other) if (other == null) throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(other))); + ActorValidationParameters = other.ActorValidationParameters; AlgorithmValidator = other.AlgorithmValidator; AudienceValidator = other.AudienceValidator; _authenticationType = other._authenticationType; ClockSkew = other.ClockSkew; ConfigurationManager = other.ConfigurationManager; + CryptoProviderFactory = other.CryptoProviderFactory; DebugId = other.DebugId; IncludeTokenOnFailedValidation = other.IncludeTokenOnFailedValidation; IgnoreTrailingSlashWhenValidatingAudience = other.IgnoreTrailingSlashWhenValidatingAudience; @@ -77,24 +83,25 @@ 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; SaveSigninToken = other.SaveSigninToken; - SignatureValidator = other.SignatureValidator; + _signatureValidator = other.SignatureValidator; TimeProvider = other.TimeProvider; TokenDecryptionKeyResolver = other.TokenDecryptionKeyResolver; - TokenDecryptionKeys = other.TokenDecryptionKeys; + _tokenDecryptionKeys = other.TokenDecryptionKeys; TokenReplayCache = other.TokenReplayCache; TokenReplayValidator = other.TokenReplayValidator; TokenTypeValidator = other.TokenTypeValidator; ValidateActor = other.ValidateActor; - ValidateSignatureLast = other.ValidateSignatureLast; ValidateWithLKG = other.ValidateWithLKG; - ValidAlgorithms = other.ValidAlgorithms; _validIssuers = other.ValidIssuers; _validAudiences = other.ValidAudiences; + _validAlgorithms = other.ValidAlgorithms; _validTokenTypes = other.ValidTypes; } @@ -111,7 +118,7 @@ public ValidationParameters() /// /// Gets or sets . /// - public ValidationParameters ActorValidationParameters { get; set; } + public ValidationParameters? ActorValidationParameters { get; set; } /// /// Allows overriding the delegate used to validate the cryptographic algorithm used. @@ -145,7 +152,7 @@ public AudienceValidationDelegate AudienceValidator /// Gets or sets the AuthenticationType when creating a . /// /// If 'value' is null or whitespace. - public string AuthenticationType + public string? AuthenticationType { get { @@ -158,7 +165,7 @@ public string AuthenticationType throw LogHelper.LogExceptionMessage(new ArgumentNullException("AuthenticationType")); } - _authenticationType = value; + _authenticationType = value!; } } @@ -230,26 +237,28 @@ public virtual ClaimsIdentity CreateClaimsIdentity(SecurityToken securityToken, //if (LogHelper.IsEnabled(EventLogLevel.Informational)) // LogHelper.LogInformation(LogMessages.IDX10245, securityToken); -#pragma warning disable RS0030 // Do not use banned APIs - return new ClaimsIdentity(authenticationType: AuthenticationType ?? DefaultAuthenticationType, nameType: nameClaimType ?? ClaimsIdentity.DefaultNameClaimType, roleType: roleClaimType ?? ClaimsIdentity.DefaultRoleClaimType); -#pragma warning disable RS0030 // Do not use banned APIs + return ClaimsIdentityFactory.Create( + authenticationType: AuthenticationType ?? DefaultAuthenticationType, + nameType: nameClaimType ?? ClaimsIdentity.DefaultNameClaimType, + roleType: roleClaimType ?? ClaimsIdentity.DefaultRoleClaimType, + securityToken); } /// /// If set, this property will be used to obtain the issuer and signing keys associated with the metadata endpoint of . /// The obtained issuer and signing keys will then be used along with those present on the ValidationParameters for validation of the incoming token. /// - public BaseConfigurationManager ConfigurationManager { get; set; } + public BaseConfigurationManager? ConfigurationManager { get; set; } /// /// Users can override the default with this property. This factory will be used for creating signature providers. /// - public CryptoProviderFactory CryptoProviderFactory { get; set; } + public CryptoProviderFactory? CryptoProviderFactory { get; set; } /// /// Gets or sets a string that helps with setting breakpoints when debugging. /// - public string DebugId { get; set; } + public string? DebugId { get; set; } /// /// Gets or sets a boolean that controls if a '/' is significant at the end of the audience. @@ -282,7 +291,10 @@ public IssuerSigningKeyValidationDelegate IssuerSigningKeyValidator /// Gets a that is unique to this instance. /// Calling will result in a new instance of this IDictionary. /// - public IDictionary InstancePropertyBag => _instancePropertyBag ??= new Dictionary(); + public IDictionary InstancePropertyBag => + _instancePropertyBag ?? + Interlocked.CompareExchange(ref _instancePropertyBag, [], null) ?? + _instancePropertyBag; /// /// Gets a value indicating if was called to obtain this instance. @@ -294,18 +306,25 @@ public IssuerSigningKeyValidationDelegate IssuerSigningKeyValidator /// /// /// This will be used to check the signature. This can be helpful when the does not contain a key identifier. - /// If both and are set, IssuerSigningKeyResolverUsingConfiguration takes - /// priority. /// - public IssuerSigningKeyResolverDelegate IssuerSigningKeyResolver { get; set; } + public IssuerSigningKeyResolverDelegate? IssuerSigningKeyResolver { get; set; } /// /// Gets the used for signature validation. /// - public IList IssuerSigningKeys => - _issuerSigningKeys ?? - Interlocked.CompareExchange(ref _issuerSigningKeys, [], null) ?? - _issuerSigningKeys; + public IList IssuerSigningKeys + { + get + { + return _issuerSigningKeys ?? + Interlocked.CompareExchange(ref _issuerSigningKeys, [], null) ?? + _issuerSigningKeys; + } + internal set + { + _issuerSigningKeys = value; + } + } /// /// Allows overriding the delegate that will be used to validate the issuer of the token. @@ -368,13 +387,15 @@ public string NameClaimType /// The issuer associated with the token. /// Returns the value that will set the property . /// - public Func NameClaimTypeRetriever { get; set; } + public Func? NameClaimTypeRetriever { get; set; } /// /// 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 { get; } + public IDictionary PropertyBag => _propertyBag ?? + Interlocked.CompareExchange(ref _propertyBag, [], null) ?? + _propertyBag; /// /// A boolean to control whether configuration should be refreshed before validating a token. @@ -418,7 +439,7 @@ public string RoleClaimType /// The issuer associated with the token. /// Returns the value that will set the property . /// - public Func RoleClaimTypeRetriever { get; set; } + public Func? RoleClaimTypeRetriever { get; set; } /// /// Gets or sets a boolean to control if the original token should be saved after the security token is validated. @@ -435,7 +456,7 @@ public string RoleClaimType /// /// If set, this delegate will be called to validate the signature of the token, instead of default processing. /// - public SignatureValidationDelegate SignatureValidator + public SignatureValidationDelegate? SignatureValidator { get { return _signatureValidator; } set { _signatureValidator = value; } @@ -452,18 +473,30 @@ public SignatureValidationDelegate SignatureValidator /// /// This will be used to decrypt the token. This can be helpful when the does not contain a key identifier. /// - internal DecryptionKeyResolverDelegate TokenDecryptionKeyResolver { get; set; } + internal DecryptionKeyResolverDelegate? TokenDecryptionKeyResolver { get; set; } /// /// Gets the that is to be used for decrypting inbound tokens. /// - public IList TokenDecryptionKeys { get; internal set; } + public IList TokenDecryptionKeys + { + get + { + return _tokenDecryptionKeys ?? + Interlocked.CompareExchange(ref _tokenDecryptionKeys, [], null) ?? + _tokenDecryptionKeys; + } + internal set + { + _tokenDecryptionKeys = value; + } + } /// /// Gets or set the that store tokens that can be checked to help detect token replay. /// /// If set, then tokens must have an expiration time or the runtime will fault. - public ITokenReplayCache TokenReplayCache { get; set; } + public ITokenReplayCache? TokenReplayCache { get; set; } /// /// Allows overriding the delegate that will be used to validate the token replay of the token. @@ -514,15 +547,6 @@ public TokenTypeValidationDelegate TokenTypeValidator [DefaultValue(false)] public bool ValidateWithLKG { get; set; } - /// - /// Gets or sets a boolean that controls the validation order of the payload and signature during token validation. - /// - /// If is set to true, it will validate payload ahead of signature. - /// The default is false. - /// - [DefaultValue(false)] - public bool ValidateSignatureLast { get; set; } - /// /// Gets or sets the valid algorithms for cryptographic operations. /// @@ -530,26 +554,56 @@ public TokenTypeValidationDelegate TokenTypeValidator /// If set to a non-empty collection, only the algorithms listed will be considered valid. /// The default is null. /// - public IList ValidAlgorithms { get; set; } + public IList ValidAlgorithms + { + get + { + return _validAlgorithms ?? + Interlocked.CompareExchange(ref _validAlgorithms, [], null) ?? + _validAlgorithms; + } + internal set + { + _validAlgorithms = value; + } + } /// /// Gets the that contains valid audiences that will be used to check against the token's audience. /// The default is an empty collection. /// - public IList ValidAudiences => - _validAudiences ?? - Interlocked.CompareExchange(ref _validAudiences, [], null) ?? - _validAudiences; + public IList ValidAudiences + { + get + { + return _validAudiences ?? + Interlocked.CompareExchange(ref _validAudiences, [], null) ?? + _validAudiences; + } + internal set + { + _validAudiences = value; + } + } /// /// Gets the that contains valid issuers that will be used to check against the token's issuer. /// The default is an empty collection. /// /// The that contains valid issuers that will be used to check against the token's 'iss' claim. - public IList ValidIssuers => - _validIssuers ?? - Interlocked.CompareExchange(ref _validIssuers, [], null) ?? - _validIssuers; + public IList ValidIssuers + { + get + { + return _validIssuers ?? + Interlocked.CompareExchange(ref _validIssuers, [], null) ?? + _validIssuers; + } + internal set + { + _validIssuers = value; + } + } /// /// Gets the that contains valid types that will be used to check against the JWT header's 'typ' claim. @@ -558,11 +612,25 @@ public TokenTypeValidationDelegate TokenTypeValidator /// The default is an empty collection. /// /// The that contains valid token types that will be used to check against the token's 'typ' claim. - public IList ValidTypes => - _validTokenTypes ?? - Interlocked.CompareExchange(ref _validTokenTypes, [], null) ?? - _validTokenTypes; + public IList ValidTypes + { + get + { + return _validTokenTypes ?? + Interlocked.CompareExchange(ref _validTokenTypes, [], null) ?? + _validTokenTypes; + } + internal set + { + _validTokenTypes = value; + } + } + /// + /// Gets or sets a boolean that controls if the actor claim should be validated. + /// + /// Default value is false. public bool ValidateActor { get; set; } } } +#nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Algorithm.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Algorithm.cs index 4950042e26..1ed23b224b 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Algorithm.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Algorithm.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Diagnostics; using System.Linq; using Microsoft.IdentityModel.Logging; @@ -26,6 +25,9 @@ internal delegate ValidationResult AlgorithmValidationDelegate( ValidationParameters validationParameters, CallContext callContext); + /// + /// Partial class for Algorithm Validation. + /// public static partial class Validators { /// @@ -35,20 +37,20 @@ public static partial class Validators /// The that signed the . /// The being validated. /// required for validation. - /// -#pragma warning disable CA1801 // TODO: remove pragma disable once callContext is used for logging + /// The that contains call information. internal static ValidationResult ValidateAlgorithm( string algorithm, +#pragma warning disable CA1801 SecurityKey securityKey, SecurityToken securityToken, ValidationParameters validationParameters, CallContext callContext) -#pragma warning restore CA1801 // TODO: remove pragma disable once callContext is used for logging +#pragma warning restore CA1801 { if (validationParameters == null) return ValidationError.NullParameter( nameof(validationParameters), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); if (validationParameters.ValidAlgorithms != null && validationParameters.ValidAlgorithms.Count > 0 && @@ -59,7 +61,7 @@ internal static ValidationResult ValidateAlgorithm( LogHelper.MarkAsNonPII(algorithm)), ValidationFailureType.AlgorithmValidationFailed, typeof(SecurityTokenInvalidAlgorithmException), - new StackFrame(true), + ValidationError.GetCurrentStackFrame(), algorithm); return algorithm; diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs index 8b0903d3dc..3c11cf948d 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs @@ -35,14 +35,14 @@ public static partial class Validators /// The audiences found in the . /// The being validated. /// The to be used for validating the token. - /// - /// If 'validationParameters' is null. - /// If 'audiences' is null and is true. - /// If is null or whitespace and is null. - /// If none of the 'audiences' matched either or one of . + /// The that contains call information. /// An EXACT match is required. -#pragma warning disable CA1801 // TODO: remove pragma disable once callContext is used for logging - internal static ValidationResult ValidateAudience(IList tokenAudiences, SecurityToken? securityToken, ValidationParameters validationParameters, CallContext callContext) + internal static ValidationResult ValidateAudience( + IList tokenAudiences, +#pragma warning disable CA1801 + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) #pragma warning restore CA1801 { if (validationParameters == null) diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Issuer.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Issuer.cs index 3bae752f35..4772b31fc4 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Issuer.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Issuer.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.IdentityModel.Logging; @@ -9,16 +8,6 @@ #nullable enable namespace Microsoft.IdentityModel.Tokens { - // TODO how do we extend this? - internal enum IssuerValidationSource - { - NotValidated = 0, - IssuerMatchedConfiguration, - IssuerMatchedValidationParameters - } - - internal record struct ValidatedIssuer(string Issuer, IssuerValidationSource ValidationSource); - /// /// Definition for delegate that will validate the issuer value in a token. /// @@ -37,7 +26,7 @@ internal delegate Task> IssuerValidationDelega CancellationToken cancellationToken); /// - /// IssuerValidation + /// Partial class for Issuer Validation. /// public static partial class Validators { @@ -66,19 +55,19 @@ internal static async Task> ValidateIssuerAsyn new MessageDetail(LogMessages.IDX10211), ValidationFailureType.IssuerValidationFailed, typeof(SecurityTokenInvalidIssuerException), - new StackFrame(true), + ValidationError.GetCurrentStackFrame(), issuer); } if (validationParameters == null) return ValidationError.NullParameter( nameof(validationParameters), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); if (securityToken == null) return ValidationError.NullParameter( nameof(securityToken), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); BaseConfiguration? configuration = null; if (validationParameters.ConfigurationManager != null) @@ -90,7 +79,7 @@ internal static async Task> ValidateIssuerAsyn new MessageDetail(LogMessages.IDX10211), ValidationFailureType.IssuerValidationFailed, typeof(SecurityTokenInvalidIssuerException), - new StackFrame(true), + ValidationError.GetCurrentStackFrame(), issuer); if (configuration != null) @@ -123,13 +112,7 @@ internal static async Task> ValidateIssuerAsyn } if (string.Equals(validationParameters.ValidIssuers[i], issuer)) - { - // TODO: Add to CallContext - //if (LogHelper.IsEnabled(EventLogLevel.Informational)) - // LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer)); - return new ValidatedIssuer(issuer!, IssuerValidationSource.IssuerMatchedValidationParameters); - } } } @@ -141,7 +124,7 @@ internal static async Task> ValidateIssuerAsyn LogHelper.MarkAsNonPII(configuration?.Issuer)), ValidationFailureType.IssuerValidationFailed, typeof(SecurityTokenInvalidIssuerException), - new StackFrame(true), + ValidationError.GetCurrentStackFrame(), issuer); } } diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs index 6e4ef95132..6d980531df 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs @@ -8,15 +8,12 @@ #nullable enable namespace Microsoft.IdentityModel.Tokens { - internal record struct ValidatedSigningKeyLifetime(DateTime? ValidFrom, DateTime? ValidTo, DateTime? ValidationTime); - /// /// Definition for delegate that will validate the that signed a . /// /// 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. @@ -24,13 +21,11 @@ internal delegate ValidationResult IssuerSigningKey SecurityKey signingKey, SecurityToken securityToken, ValidationParameters validationParameters, - BaseConfiguration? configuration, CallContext callContext); /// - /// SigningKeyValidation + /// Partial class for Issuer Signing Key Validation. /// - public static partial class Validators { /// @@ -39,19 +34,12 @@ 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 to be used for logging. - /// if 'securityKey' is null and ValidateIssuerSigningKey is true. - /// if 'securityToken' is null and ValidateIssuerSigningKey is true. - /// if 'validationParameters' is null. + /// 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) + CallContext callContext) { if (validationParameters == null) return IssuerSigningKeyValidationError.NullParameter( @@ -79,12 +67,12 @@ internal static ValidationResult ValidateIssuerSign /// /// The that signed the . /// The to be used for validating the token. - /// + /// The that contains call information. #pragma warning disable CA1801 // Review unused parameters internal static ValidationResult ValidateIssuerSigningKeyLifeTime( SecurityKey securityKey, ValidationParameters validationParameters, - CallContext? callContext) + CallContext callContext) #pragma warning restore CA1801 // Review unused parameters { DateTime utcNow = validationParameters.TimeProvider.GetUtcNow().UtcDateTime; diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs index 6f7262e40f..7aa37577b3 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs @@ -7,8 +7,6 @@ #nullable enable namespace Microsoft.IdentityModel.Tokens { - internal record struct ValidatedLifetime(DateTime? NotBefore, DateTime? Expires); - /// /// Definition for delegate that will validate the lifetime of a . /// @@ -16,7 +14,7 @@ internal record struct ValidatedLifetime(DateTime? NotBefore, DateTime? Expires) /// The 'expiration' time found in the . /// The that is being validated. /// The to be used for validating the token. - /// + /// The that contains call information. /// A that contains the results of validating the issuer. /// This delegate is not expected to throw. internal delegate ValidationResult LifetimeValidationDelegate( @@ -27,7 +25,7 @@ internal delegate ValidationResult LifetimeValidationDelegate CallContext callContext); /// - /// IssuerValidation + /// Partial class for Lifetime Validation. /// public static partial class Validators { @@ -38,15 +36,10 @@ public static partial class Validators /// The 'expiration' time found in the . /// The being validated. /// The to be used for validating the token. - /// + /// The that contains call information. /// A indicating whether validation was successful, and providing a if it was not. - /// If 'validationParameters' is null. - /// If 'expires.HasValue' is false. - /// If 'notBefore' is > 'expires'. - /// If 'notBefore' is > DateTime.UtcNow. - /// If 'expires' is < DateTime.UtcNow. /// All time comparisons apply . -#pragma warning disable CA1801 // TODO: remove pragma disable once callContext is used for logging +#pragma warning disable CA1801 internal static ValidationResult ValidateLifetime( DateTime? notBefore, DateTime? expires, diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenReplay.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenReplay.cs index 3e8de4ffd9..f25292e5cb 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenReplay.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenReplay.cs @@ -3,6 +3,7 @@ using System; +#nullable enable namespace Microsoft.IdentityModel.Tokens { /// @@ -11,7 +12,7 @@ namespace Microsoft.IdentityModel.Tokens /// When does the expire.. /// The security token that is being validated. /// The to be used for validating the token. - /// + /// The that contains call information. /// A that contains the results of validating the token. /// This delegate is not expected to throw. internal delegate ValidationResult TokenReplayValidationDelegate( @@ -31,14 +32,13 @@ public static partial class Validators /// When does the security token expire. /// The being validated. /// The to be used for validating the token. - /// - /// If 'securityToken' is null or whitespace. - /// If 'validationParameters' is null or whitespace. - /// If is not null and expirationTime.HasValue is false. When a TokenReplayCache is set, tokens require an expiration time. - /// If the 'securityToken' is found in the cache. - /// If the 'securityToken' could not be added to the . + /// The that contains call information. #pragma warning disable CA1801 // Review unused parameters - internal static ValidationResult ValidateTokenReplay(DateTime? expirationTime, string securityToken, ValidationParameters validationParameters, CallContext callContext) + internal static ValidationResult ValidateTokenReplay( + DateTime? expirationTime, + string securityToken, + ValidationParameters validationParameters, + CallContext callContext) #pragma warning restore CA1801 // Review unused parameters { if (string.IsNullOrWhiteSpace(securityToken)) @@ -92,3 +92,4 @@ public static partial class Validators } } } +#nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs index a95a60b363..2ccce42550 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs @@ -8,14 +8,13 @@ #nullable enable namespace Microsoft.IdentityModel.Tokens { - internal record struct ValidatedTokenType(string Type, int ValidTypeCount); /// /// Definition for delegate that will validate the token type of a token. /// /// The token type or null if it couldn't be resolved (e.g from the 'typ' header for a JWT). /// The that is being validated. /// required for validation. - /// + /// The that contains call information. /// A that contains the results of validating the token type. /// An EXACT match is required. (case sensitive) is used for comparing against . internal delegate ValidationResult TokenTypeValidationDelegate( @@ -24,6 +23,9 @@ internal delegate ValidationResult TokenTypeValidationDelega ValidationParameters validationParameters, CallContext callContext); + /// + /// Partial class for Token Type Validation. + /// public static partial class Validators { /// @@ -32,16 +34,16 @@ public static partial class Validators /// The token type or null if it couldn't be resolved (e.g from the 'typ' header for a JWT). /// The that is being validated. /// required for validation. - /// + /// The that contains call information. /// A that contains the results of validating the token type. /// An EXACT match is required. (case sensitive) is used for comparing against . -#pragma warning disable CA1801 // TODO: remove pragma disable once callContext is used for logging +#pragma warning disable CA1801 internal static ValidationResult ValidateTokenType( string? type, SecurityToken? securityToken, ValidationParameters validationParameters, CallContext callContext) -#pragma warning restore CA1801 // TODO: remove pragma disable once callContext is used for logging +#pragma warning restore CA1801 { if (securityToken == null) return TokenTypeValidationError.NullParameter( 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.JsonWebTokens.Tests/JsonWebTokenHandler.ValidateTokenAsyncTests.Algorithm.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ValidateTokenAsyncTests.Algorithm.cs index b26524130c..b6872c70e9 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ValidateTokenAsyncTests.Algorithm.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ValidateTokenAsyncTests.Algorithm.cs @@ -103,13 +103,14 @@ static TokenValidationParameters CreateTokenValidationParameters( static ValidationParameters CreateValidationParameters( SecurityKey? signingKey = null, List? validAlgorithms = null) { - ValidationParameters validationParameters = new ValidationParameters(); + ValidationParameters validationParameters = new ValidationParameters + { + ValidAlgorithms = validAlgorithms ?? [] + }; if (signingKey is not null) validationParameters.IssuerSigningKeys.Add(signingKey); - validationParameters.ValidAlgorithms = validAlgorithms; - // Skip all validations except signature and algorithm validationParameters.AudienceValidator = SkipValidationDelegates.SkipAudienceValidation; validationParameters.IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation; diff --git a/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs b/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs index f3152b4d34..dc7f78345b 100644 --- a/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs +++ b/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs @@ -1331,11 +1331,11 @@ internal static bool AreValidatedIssuersEqual(ValidatedIssuer validatedIssuer1, "validatedIssuer2.Issuer", localContext); - AreIntsEqual( - (int)validatedIssuer1.ValidationSource, - (int)validatedIssuer2.ValidationSource, - "validatedIssuer1.ValidationSource", - "validatedIssuer2.ValidationSource", + AreStringsEqual( + validatedIssuer1.ValidationSource.Name, + validatedIssuer2.ValidationSource.Name, + "validatedIssuer1.ValidationSource.Name", + "validatedIssuer2.ValidationSource.Name", localContext); return context.Merge(localContext); 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.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs index 7571d4f8aa..6989427b5c 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs @@ -29,7 +29,7 @@ public CustomIssuerValidationError( { } - internal override Exception GetException() + protected override Exception CreateException() { if (ExceptionType == typeof(CustomSecurityTokenInvalidIssuerException)) { @@ -39,7 +39,7 @@ internal override Exception GetException() return exception; } - return base.GetException(); + return base.CreateException(); } } @@ -76,7 +76,7 @@ public CustomAudienceValidationError( { } - internal override Exception GetException() + protected override Exception CreateException() { if (ExceptionType == typeof(CustomSecurityTokenInvalidAudienceException)) { @@ -86,7 +86,7 @@ internal override Exception GetException() return exception; } - return base.GetException(); + return base.CreateException(); } } @@ -126,7 +126,7 @@ public CustomLifetimeValidationError( { } - internal override Exception GetException() + protected override Exception CreateException() { if (ExceptionType == typeof(CustomSecurityTokenInvalidLifetimeException)) { @@ -136,7 +136,7 @@ internal override Exception GetException() return exception; } - return base.GetException(); + return base.CreateException(); } } @@ -176,7 +176,7 @@ public CustomIssuerSigningKeyValidationError( { } - internal override Exception GetException() + protected override Exception CreateException() { if (ExceptionType == typeof(CustomSecurityTokenInvalidSigningKeyException)) { @@ -184,7 +184,7 @@ internal override Exception GetException() exception.SetValidationError(this); return exception; } - return base.GetException(); + return base.CreateException(); } } @@ -221,7 +221,7 @@ public CustomTokenTypeValidationError( : base(messageDetail, validationFailureType, exceptionType, stackFrame, invalidTokenType, innerException) { } - internal override Exception GetException() + protected override Exception CreateException() { if (ExceptionType == typeof(CustomSecurityTokenInvalidTypeException)) { @@ -229,7 +229,7 @@ internal override Exception GetException() exception.SetValidationError(this); return exception; } - return base.GetException(); + return base.CreateException(); } } @@ -266,7 +266,7 @@ public CustomSignatureValidationError( base(messageDetail, validationFailureType, exceptionType, stackFrame, innerValidationError, innerException) { } - internal override Exception GetException() + protected override Exception CreateException() { if (ExceptionType == typeof(CustomSecurityTokenInvalidSignatureException)) { @@ -274,7 +274,7 @@ internal override Exception GetException() exception.SetValidationError(this); return exception; } - return base.GetException(); + return base.CreateException(); } } @@ -312,7 +312,7 @@ public CustomAlgorithmValidationError( : base(messageDetail, validationFailureType, exceptionType, stackFrame, algorithm, innerException) { } - internal override Exception GetException() + protected override Exception CreateException() { if (ExceptionType == typeof(CustomSecurityTokenInvalidAlgorithmException)) { @@ -320,7 +320,7 @@ internal override Exception GetException() exception.SetValidationError(this); return exception; } - return base.GetException(); + return base.CreateException(); } } @@ -359,7 +359,7 @@ public CustomTokenReplayValidationError( { } - internal override Exception GetException() + protected override Exception CreateException() { if (ExceptionType == typeof(CustomSecurityTokenReplayDetectedException)) { @@ -369,7 +369,7 @@ internal override Exception GetException() return exception; } - return base.GetException(); + return base.CreateException(); } } diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.Algorithm.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.Algorithm.cs index 13581b3d01..17fdda1df1 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.Algorithm.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.Algorithm.cs @@ -143,12 +143,11 @@ public static TheoryData ValidateTokenAsy static ValidationParameters CreateValidationParameters( SecurityKey? signingKey = null, List? validAlgorithms = null, bool tryAllKeys = false) { - ValidationParameters validationParameters = new ValidationParameters(); - - if (signingKey is not null) - validationParameters.IssuerSigningKeys.Add(signingKey); - - validationParameters.ValidAlgorithms = validAlgorithms; + ValidationParameters validationParameters = new ValidationParameters() + { + ValidAlgorithms = validAlgorithms ?? [], + IssuerSigningKeys = signingKey is not null ? [signingKey] : [], + }; validationParameters.AudienceValidator = SkipValidationDelegates.SkipAudienceValidation; validationParameters.IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation; diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.Algorithm.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.Algorithm.cs index 5d1a77c4c6..258cda2eb4 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.Algorithm.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.Algorithm.cs @@ -142,12 +142,11 @@ public static TheoryData ValidateTokenAsy static ValidationParameters CreateValidationParameters( SecurityKey? signingKey = null, List? validAlgorithms = null, bool tryAllKeys = false) { - ValidationParameters validationParameters = new ValidationParameters(); - - if (signingKey is not null) - validationParameters.IssuerSigningKeys.Add(signingKey); - - validationParameters.ValidAlgorithms = validAlgorithms; + ValidationParameters validationParameters = new ValidationParameters() + { + ValidAlgorithms = validAlgorithms ?? [], + IssuerSigningKeys = signingKey is not null ? [signingKey] : [], + }; validationParameters.AudienceValidator = SkipValidationDelegates.SkipAudienceValidation; validationParameters.IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation; 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