From a39f45b4128728de9f1e125ea089cbb5b98789a7 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Wed, 20 Nov 2024 18:20:50 +0000 Subject: [PATCH 1/5] Updated TokenTypeValidationError and updated the default token type validation delegate to use it --- .../InternalAPI.Unshipped.txt | 4 +++- .../Results/Details/TokenTypeValidationError.cs | 16 ++++++++++++---- .../Validation/Validators.TokenType.cs | 12 ++++++------ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt index 03e642a883..2ca7213ac0 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -20,8 +20,9 @@ Microsoft.IdentityModel.Tokens.LifetimeValidationError.LifetimeValidationError(M Microsoft.IdentityModel.Tokens.LifetimeValidationError.NotBefore.get -> System.DateTime Microsoft.IdentityModel.Tokens.LifetimeValidationError.NotBefore.set -> void Microsoft.IdentityModel.Tokens.TokenTypeValidationError +Microsoft.IdentityModel.Tokens.TokenTypeValidationError.InvalidTokenType.get -> string +Microsoft.IdentityModel.Tokens.TokenTypeValidationError.InvalidTokenType.set -> void Microsoft.IdentityModel.Tokens.TokenTypeValidationError.TokenTypeValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidTokenType, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType = null, System.Exception innerException = null) -> void -Microsoft.IdentityModel.Tokens.TokenTypeValidationError._invalidTokenType -> string Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.get -> System.TimeProvider Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.set -> void Microsoft.IdentityModel.Tokens.ValidationError.AddCurrentStackFrame(string filePath = "", int lineNumber = 0, int skipFrames = 1) -> Microsoft.IdentityModel.Tokens.ValidationError @@ -39,6 +40,7 @@ static Microsoft.IdentityModel.Tokens.AudienceValidationError.AudiencesNull -> S static Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidateAudienceFailed -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidationParametersAudiencesCountZero -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidationParametersNull -> System.Diagnostics.StackFrame +static Microsoft.IdentityModel.Tokens.TokenTypeValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.TokenTypeValidationError static Microsoft.IdentityModel.Tokens.Utility.SerializeAsSingleCommaDelimitedString(System.Collections.Generic.IList strings) -> string static Microsoft.IdentityModel.Tokens.ValidationError.GetCurrentStackFrame(string filePath = "", int lineNumber = 0, int skipFrames = 1) -> System.Diagnostics.StackFrame static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.IssuerValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenTypeValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenTypeValidationError.cs index 9f633ed741..019a2d8b67 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenTypeValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenTypeValidationError.cs @@ -9,8 +9,6 @@ namespace Microsoft.IdentityModel.Tokens { internal class TokenTypeValidationError : ValidationError { - protected string? _invalidTokenType; - internal TokenTypeValidationError( MessageDetail messageDetail, Type exceptionType, @@ -20,7 +18,7 @@ internal TokenTypeValidationError( Exception? innerException = null) : base(messageDetail, exceptionType, stackFrame, validationFailureType ?? ValidationFailureType.TokenTypeValidationFailed, innerException) { - _invalidTokenType = invalidTokenType; + InvalidTokenType = invalidTokenType; } internal override Exception GetException() @@ -29,14 +27,24 @@ internal override Exception GetException() { SecurityTokenInvalidTypeException exception = new(MessageDetail.Message, InnerException) { - InvalidType = _invalidTokenType + InvalidType = InvalidTokenType }; + exception.SetValidationError(this); return exception; } return base.GetException(); } + + internal static new TokenTypeValidationError NullParameter(string parameterName, StackFrame stackFrame) => new( + MessageDetail.NullParameter(parameterName), + typeof(SecurityTokenArgumentNullException), + stackFrame, + null, // invalidTokenType + ValidationFailureType.NullArgument); + + protected string? InvalidTokenType { get; set; } } } #nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs index 175cc190f7..7ff0e48656 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs @@ -45,14 +45,14 @@ internal static ValidationResult ValidateTokenType( #pragma warning restore CA1801 // TODO: remove pragma disable once callContext is used for logging { if (securityToken == null) - return ValidationError.NullParameter( + return TokenTypeValidationError.NullParameter( nameof(securityToken), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); if (validationParameters == null) - return ValidationError.NullParameter( + return TokenTypeValidationError.NullParameter( nameof(validationParameters), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); if (validationParameters.ValidTypes.Count == 0) { @@ -65,7 +65,7 @@ internal static ValidationResult ValidateTokenType( return new TokenTypeValidationError( new MessageDetail(LogMessages.IDX10256), typeof(SecurityTokenInvalidTypeException), - new StackFrame(true), + ValidationError.GetCurrentStackFrame(), null); // even if it is empty, we report null to match the original behaviour. if (!validationParameters.ValidTypes.Contains(type, StringComparer.Ordinal)) @@ -76,7 +76,7 @@ internal static ValidationResult ValidateTokenType( LogHelper.MarkAsNonPII(type), LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidTypes))), typeof(SecurityTokenInvalidTypeException), - new StackFrame(true), + ValidationError.GetCurrentStackFrame(), type); } From 37a0a07b678c1a126cbce4ecaba1b1b8e74f1343 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Wed, 20 Nov 2024 18:23:01 +0000 Subject: [PATCH 2/5] Added log message and validation failure type for the case where the token type validation delegate throws --- src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt | 2 ++ src/Microsoft.IdentityModel.Tokens/LogMessages.cs | 2 +- .../Validation/ValidationFailureType.cs | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt index 2ca7213ac0..0037390bb8 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -1,6 +1,7 @@ const Microsoft.IdentityModel.Tokens.LogMessages.IDX10002 = "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'." -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10268 = "IDX10268: Unable to validate audience, validationParameters.ValidAudiences.Count == 0." -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10269 = "IDX10269: IssuerValidationDelegate threw an exception, see inner exception." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10275 = "IDX10275: TokenTypeValidationDelegate threw an exception, see inner exception." -> string Microsoft.IdentityModel.Tokens.AlgorithmValidationError Microsoft.IdentityModel.Tokens.AlgorithmValidationError.AlgorithmValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidAlgorithm, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType = null, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.AlgorithmValidationError.InvalidAlgorithm.get -> string @@ -49,4 +50,5 @@ static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.NoValidatio static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.SignatureAlgorithmValidationFailed -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.TokenExceedsMaximumSize -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.TokenIsNotSigned -> 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 diff --git a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs index 59c48703f3..8e2cecae39 100644 --- a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs +++ b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs @@ -88,7 +88,7 @@ internal static class LogMessages public const string IDX10267 = "IDX10267: '{0}' has been called by a derived class '{1}' which has not implemented this method. For this call graph to succeed, '{1}' will need to implement '{0}'."; public const string IDX10268 = "IDX10268: Unable to validate audience, validationParameters.ValidAudiences.Count == 0."; public const string IDX10269 = "IDX10269: IssuerValidationDelegate threw an exception, see inner exception."; - + public const string IDX10275 = "IDX10275: TokenTypeValidationDelegate threw an exception, see inner exception."; // 10500 - SignatureValidation public const string IDX10500 = "IDX10500: Signature validation failed. No security keys were provided to validate the signature."; diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs index e512d2af5d..e67ee93c7f 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs @@ -134,5 +134,10 @@ private class XmlValidationFailure : ValidationFailureType { internal XmlValidat /// public static readonly ValidationFailureType IssuerValidatorThrew = new IssuerValidatorFailure("IssuerValidatorThrew"); private class IssuerValidatorFailure : ValidationFailureType { internal IssuerValidatorFailure(string name) : base(name) { } } + + /// + /// Defines a type that represents that the token type validation delegate threw and exception. + /// + public static readonly ValidationFailureType TokenTypeValidatorThrew = new TokenTypeValidationFailure("TokenTypeValidatorThrew"); } } From 058c87eda4e953edbac19a04002c58ebd2ca55c6 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Wed, 20 Nov 2024 18:23:29 +0000 Subject: [PATCH 3/5] Added custom validation error and validation delegates for token type validation --- .../CustomTokenTypeValidationDelegates.cs | 135 ++++++++++++++++++ .../CustomValidationErrors.cs | 46 ++++++ 2 files changed, 181 insertions(+) create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomTokenTypeValidationDelegates.cs diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomTokenTypeValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomTokenTypeValidationDelegates.cs new file mode 100644 index 0000000000..6eb548fbcb --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomTokenTypeValidationDelegates.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.IdentityModel.Tokens; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils +{ + internal class CustomTokenTypeValidationDelegates + { + internal static ValidationResult CustomTokenTypeValidatorDelegate( + string? type, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + // Returns a CustomTokenTypeValidationError : TokenTypeValidationError + return new CustomTokenTypeValidationError( + new MessageDetail(nameof(CustomTokenTypeValidatorDelegate), null), + typeof(SecurityTokenInvalidTypeException), + ValidationError.GetCurrentStackFrame(), + type, + null); + } + + internal static ValidationResult CustomTokenTypeValidatorCustomExceptionDelegate( + string? type, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomTokenTypeValidationError( + new MessageDetail(nameof(CustomTokenTypeValidatorCustomExceptionDelegate), null), + typeof(CustomSecurityTokenInvalidTypeException), + ValidationError.GetCurrentStackFrame(), + type, + null); + } + + internal static ValidationResult CustomTokenTypeValidatorCustomExceptionCustomFailureTypeDelegate( + string? type, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomTokenTypeValidationError( + new MessageDetail(nameof(CustomTokenTypeValidatorCustomExceptionCustomFailureTypeDelegate), null), + typeof(CustomSecurityTokenInvalidTypeException), + ValidationError.GetCurrentStackFrame(), + type, + CustomTokenTypeValidationError.CustomTokenTypeValidationFailureType); + } + + internal static ValidationResult CustomTokenTypeValidatorUnknownExceptionDelegate( + string? type, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomTokenTypeValidationError( + new MessageDetail(nameof(CustomTokenTypeValidatorUnknownExceptionDelegate), null), + typeof(NotSupportedException), + ValidationError.GetCurrentStackFrame(), + type, + null); + } + + internal static ValidationResult CustomTokenTypeValidatorWithoutGetExceptionOverrideDelegate( + string? type, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomTokenTypeWithoutGetExceptionValidationOverrideError( + new MessageDetail(nameof(CustomTokenTypeValidatorWithoutGetExceptionOverrideDelegate), null), + typeof(CustomSecurityTokenInvalidTypeException), + ValidationError.GetCurrentStackFrame(), + type, + null); + } + + internal static ValidationResult TokenTypeValidatorDelegate( + string? type, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new TokenTypeValidationError( + new MessageDetail(nameof(TokenTypeValidatorDelegate), null), + typeof(SecurityTokenInvalidTypeException), + ValidationError.GetCurrentStackFrame(), + type, + null); + } + + internal static ValidationResult TokenTypeValidatorThrows( + string? type, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + throw new CustomSecurityTokenInvalidTypeException(nameof(TokenTypeValidatorThrows), null); + } + + internal static ValidationResult TokenTypeValidatorCustomTokenTypeExceptionTypeDelegate( + string? type, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new TokenTypeValidationError( + new MessageDetail(nameof(TokenTypeValidatorCustomTokenTypeExceptionTypeDelegate), null), + typeof(CustomSecurityTokenInvalidTypeException), + ValidationError.GetCurrentStackFrame(), + type, + null); + } + + internal static ValidationResult TokenTypeValidatorCustomExceptionTypeDelegate( + string? type, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new TokenTypeValidationError( + new MessageDetail(nameof(TokenTypeValidatorCustomExceptionTypeDelegate), null), + typeof(CustomSecurityTokenException), + ValidationError.GetCurrentStackFrame(), + type, + null); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs index e5274bd2f8..0eef52a71f 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs @@ -157,6 +157,52 @@ public CustomLifetimeWithoutGetExceptionValidationOverrideError( } #endregion + #region TokenTypeValidationErrors + internal class CustomTokenTypeValidationError : TokenTypeValidationError + { + /// + /// A custom validation failure type. + /// + public static readonly ValidationFailureType CustomTokenTypeValidationFailureType = new TokenTypeValidationFailure("CustomTokenTypeValidationFailureType"); + private class TokenTypeValidationFailure : ValidationFailureType { internal TokenTypeValidationFailure(string name) : base(name) { } } + + public CustomTokenTypeValidationError( + MessageDetail messageDetail, + Type exceptionType, + StackFrame stackFrame, + string? invalidTokenType, + ValidationFailureType? validationFailureType = null, + Exception? innerException = null) + : base(messageDetail, exceptionType, stackFrame, invalidTokenType, validationFailureType, innerException) + { + } + internal override Exception GetException() + { + if (ExceptionType == typeof(CustomSecurityTokenInvalidTypeException)) + { + var exception = new CustomSecurityTokenInvalidTypeException(MessageDetail.Message, InnerException) { InvalidType = InvalidTokenType }; + exception.SetValidationError(this); + return exception; + } + return base.GetException(); + } + } + + internal class CustomTokenTypeWithoutGetExceptionValidationOverrideError : TokenTypeValidationError + { + public CustomTokenTypeWithoutGetExceptionValidationOverrideError( + MessageDetail messageDetail, + Type exceptionType, + StackFrame stackFrame, + string? invalidTokenType, + ValidationFailureType? validationFailureType = null, + Exception? innerException = null) + : base(messageDetail, exceptionType, stackFrame, invalidTokenType, validationFailureType, innerException) + { + } + } + #endregion // TokenTypeValidationErrors + // Other custom validation errors to be added here for signature validation, issuer signing key, etc. } #nullable restore From 63ac32598ea0e9db45d30315174388f7f5885f8c Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Wed, 20 Nov 2024 18:23:52 +0000 Subject: [PATCH 4/5] Handle the case where the token type validation delegate throws in JsonWebTokenHandler --- ...nWebTokenHandler.ValidateToken.Internal.cs | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs index c2ec88ac19..f84cc9dccd 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs @@ -336,12 +336,28 @@ await ValidateJWSAsync(actorToken, actorParameters, configuration, callContext, actorValidationResult = innerActorValidationResult; } - ValidationResult typeValidationResult = validationParameters.TokenTypeValidator( - jsonWebToken.Typ, jsonWebToken, validationParameters, callContext); - if (!typeValidationResult.IsValid) + ValidationResult typeValidationResult; + + try { - StackFrame typeValidationFailureStackFrame = StackFrames.TypeValidationFailed ??= new StackFrame(true); - return typeValidationResult.UnwrapError().AddStackFrame(typeValidationFailureStackFrame); + typeValidationResult = validationParameters.TokenTypeValidator( + jsonWebToken.Typ, jsonWebToken, validationParameters, callContext); + + if (!typeValidationResult.IsValid) + return typeValidationResult.UnwrapError().AddCurrentStackFrame(); + } + +#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 TokenTypeValidationError( + new MessageDetail(TokenLogMessages.IDX10275), + typeof(SecurityTokenInvalidTypeException), + ValidationError.GetCurrentStackFrame(), + jsonWebToken.Typ, + ValidationFailureType.TokenTypeValidatorThrew, + ex); } // The signature validation delegate is yet to be migrated to ValidationParameters. From 658ac48e52f05aa5b4adb2004f9f0683e968c892 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Wed, 20 Nov 2024 18:24:09 +0000 Subject: [PATCH 5/5] Added extensibility tests for token type validation on JWT --- ...WebTokenHandler.Extensibility.TokenType.cs | 263 ++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenType.cs diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenType.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenType.cs new file mode 100644 index 0000000000..b791fe7bc2 --- /dev/null +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenType.cs @@ -0,0 +1,263 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.JsonWebTokens.Tests; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.TestUtils; +using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Tokens.Json.Tests; +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.JsonWebTokens.Extensibility.Tests +{ + public partial class JsonWebTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(TokenType_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_TokenTypeValidator_Extensibility(TokenTypeExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_TokenTypeValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.TokenTypeValidationError!.AddStackFrame(new StackFrame(false)); + + try + { + ValidationResult validationResult = await theoryData.JsonWebTokenHandler.ValidateTokenAsync( + theoryData.JsonWebToken!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + context.Diffs.Add("validationResult.IsValid is true, expected false"); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.TokenTypeValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData TokenType_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + + #region return CustomTokenTypeValidationError + // Test cases where delegate is overridden and return a CustomTokenTypeValidationError + // CustomTokenTypeValidationError : TokenTypeValidationError, ExceptionType: SecurityTokenInvalidTypeException + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "CustomTokenTypeValidatorDelegate", + CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidTypeException), + nameof(CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorDelegate)), + TokenTypeValidationError = new CustomTokenTypeValidationError( + new MessageDetail( + nameof(CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorDelegate), null), + typeof(SecurityTokenInvalidTypeException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 160), + "JWT", + ValidationFailureType.TokenTypeValidationFailed) + }); + + // CustomTokenTypeValidationError : TokenTypeValidationError, ExceptionType: CustomSecurityTokenInvalidTypeException : SecurityTokenInvalidTypeException + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "CustomTokenTypeValidatorCustomExceptionDelegate", + CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorCustomExceptionDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidTypeException), + nameof(CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorCustomExceptionDelegate)), + TokenTypeValidationError = new CustomTokenTypeValidationError( + new MessageDetail( + nameof(CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorCustomExceptionDelegate), null), + typeof(CustomSecurityTokenInvalidTypeException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 175), + "JWT", + ValidationFailureType.TokenTypeValidationFailed), + }); + + // CustomTokenTypeValidationError : TokenTypeValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "CustomTokenTypeValidatorUnknownExceptionDelegate", + CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorUnknownExceptionDelegate, + extraStackFrames: 2) + { + // CustomTokenTypeValidationError does not handle the exception type 'NotSupportedException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(NotSupportedException), + nameof(CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorUnknownExceptionDelegate))), + TokenTypeValidationError = new CustomTokenTypeValidationError( + new MessageDetail( + nameof(CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorUnknownExceptionDelegate), null), + typeof(NotSupportedException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 205), + "JWT", + ValidationFailureType.TokenTypeValidationFailed), + }); + + // CustomTokenTypeValidationError : TokenTypeValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "CustomTokenTypeValidatorCustomExceptionCustomFailureTypeDelegate", + CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidTypeException), + nameof(CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorCustomExceptionCustomFailureTypeDelegate)), + TokenTypeValidationError = new CustomTokenTypeValidationError( + new MessageDetail( + nameof(CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorCustomExceptionCustomFailureTypeDelegate), null), + typeof(CustomSecurityTokenInvalidTypeException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 190), + "JWT", + CustomTokenTypeValidationError.CustomTokenTypeValidationFailureType), + }); + #endregion + + #region return TokenTypeValidationError + // Test cases where delegate is overridden and return an TokenTypeValidationError + // TokenTypeValidationError : ValidationError, ExceptionType: SecurityTokenInvalidTypeException + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "TokenTypeValidatorDelegate", + CustomTokenTypeValidationDelegates.TokenTypeValidatorDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidTypeException), + nameof(CustomTokenTypeValidationDelegates.TokenTypeValidatorDelegate)), + TokenTypeValidationError = new TokenTypeValidationError( + new MessageDetail( + nameof(CustomTokenTypeValidationDelegates.TokenTypeValidatorDelegate), null), + typeof(SecurityTokenInvalidTypeException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 235), + "JWT", + ValidationFailureType.TokenTypeValidationFailed) + }); + + // TokenTypeValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidTypeException : SecurityTokenInvalidTypeException + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "TokenTypeValidatorCustomTokenTypeExceptionTypeDelegate", + CustomTokenTypeValidationDelegates.TokenTypeValidatorCustomTokenTypeExceptionTypeDelegate, + extraStackFrames: 2) + { + // TokenTypeValidationError does not handle the exception type 'CustomSecurityTokenInvalidTypeException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidTypeException), + nameof(CustomTokenTypeValidationDelegates.TokenTypeValidatorCustomTokenTypeExceptionTypeDelegate))), + TokenTypeValidationError = new TokenTypeValidationError( + new MessageDetail( + nameof(CustomTokenTypeValidationDelegates.TokenTypeValidatorCustomTokenTypeExceptionTypeDelegate), null), + typeof(CustomSecurityTokenInvalidTypeException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 259), + "JWT", + ValidationFailureType.TokenTypeValidationFailed) + }); + + // TokenTypeValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "TokenTypeValidatorCustomExceptionTypeDelegate", + CustomTokenTypeValidationDelegates.TokenTypeValidatorCustomExceptionTypeDelegate, + extraStackFrames: 2) + { + // TokenTypeValidationError does not handle the exception type 'CustomSecurityTokenException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenException), + nameof(CustomTokenTypeValidationDelegates.TokenTypeValidatorCustomExceptionTypeDelegate))), + TokenTypeValidationError = new TokenTypeValidationError( + new MessageDetail( + nameof(CustomTokenTypeValidationDelegates.TokenTypeValidatorCustomExceptionTypeDelegate), null), + typeof(CustomSecurityTokenException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 274), + "JWT", + ValidationFailureType.TokenTypeValidationFailed) + }); + + // TokenTypeValidationError : ValidationError, ExceptionType: SecurityTokenInvalidTypeException, inner: CustomSecurityTokenInvalidTypeException + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "TokenTypeValidatorThrows", + CustomTokenTypeValidationDelegates.TokenTypeValidatorThrows, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidTypeException), + string.Format(Tokens.LogMessages.IDX10275), + typeof(CustomSecurityTokenInvalidTypeException)), + TokenTypeValidationError = new TokenTypeValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10275), null), + typeof(SecurityTokenInvalidTypeException), + new StackFrame("JsonWebTokenHandler.ValidateToken.Internal.cs", 250), + "JWT", + ValidationFailureType.TokenTypeValidatorThrew, + new SecurityTokenInvalidTypeException(nameof(CustomTokenTypeValidationDelegates.TokenTypeValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } + + public class TokenTypeExtensibilityTheoryData : ValidateTokenAsyncBaseTheoryData + { + internal TokenTypeExtensibilityTheoryData(string testId, TokenTypeValidationDelegate tokenTypeValidator, int extraStackFrames) : base(testId) + { + JsonWebToken = JsonUtilities.CreateUnsignedJsonWebToken("iss", "issuer"); + + ValidationParameters = new ValidationParameters + { + AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + SignatureValidator = SkipValidationDelegates.SkipSignatureValidation, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = tokenTypeValidator + }; + + ExtraStackFrames = extraStackFrames; + } + + public JsonWebToken JsonWebToken { get; } + + public JsonWebTokenHandler JsonWebTokenHandler { get; } = new JsonWebTokenHandler(); + + public bool IsValid { get; set; } + + internal ValidatedTokenType ValidatedTokenType { get; set; } + + internal TokenTypeValidationError? TokenTypeValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } + } +} +#nullable restore