From 9463f20e0e5e173e7904351af187d4529ecb2147 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Mon, 25 Nov 2024 11:35:05 +0000 Subject: [PATCH] Extensibility tests: Token Type - JWT (#3030) * Updated TokenTypeValidationError and updated the default token type validation delegate to use it * Added log message and validation failure type for the case where the token type validation delegate throws * Added custom validation error and validation delegates for token type validation (cherry picked from commit 058c87eda4e953edbac19a04002c58ebd2ca55c6) * Handle the case where the token type validation delegate throws in JsonWebTokenHandler (cherry picked from commit 63ac32598ea0e9db45d30315174388f7f5885f8c) * Added extensibility tests for token type validation on JWT (cherry picked from commit 658ac48e52f05aa5b4adb2004f9f0683e968c892) * Updated validation failure type position in tests * Update src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs Co-authored-by: Westin Musser <127992899+westin-m@users.noreply.github.com> --------- Co-authored-by: Westin Musser <127992899+westin-m@users.noreply.github.com> --- ...nWebTokenHandler.ValidateToken.Internal.cs | 26 +- .../InternalAPI.Unshipped.txt | 5 +- .../LogMessages.cs | 2 +- .../Details/TokenTypeValidationError.cs | 16 +- .../Validation/ValidationFailureType.cs | 5 + .../Validation/Validators.TokenType.cs | 13 +- ...WebTokenHandler.Extensibility.TokenType.cs | 263 ++++++++++++++++++ .../CustomTokenTypeValidationDelegates.cs | 141 ++++++++++ .../CustomValidationErrors.cs | 45 +++ 9 files changed, 496 insertions(+), 20 deletions(-) create mode 100644 test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenType.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomTokenTypeValidationDelegates.cs diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs index 18fa21fcf9..ded8b16947 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), + ValidationFailureType.TokenTypeValidatorThrew, + typeof(SecurityTokenInvalidTypeException), + ValidationError.GetCurrentStackFrame(), + jsonWebToken.Typ, + ex); } // The signature validation delegate is yet to be migrated to ValidationParameters. diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt index 23c18b30a7..15dcd06514 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -1,7 +1,6 @@ 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.IDX10271 = "IDX10271: LifetimeValidationDelegate 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, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidAlgorithm, System.Exception innerException = null) -> void @@ -25,7 +24,7 @@ Microsoft.IdentityModel.Tokens.LifetimeValidationError.LifetimeValidationError(M Microsoft.IdentityModel.Tokens.LifetimeValidationError.NotBefore.get -> System.DateTime? Microsoft.IdentityModel.Tokens.TokenTypeValidationError Microsoft.IdentityModel.Tokens.TokenTypeValidationError.TokenTypeValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidTokenType, System.Exception innerException = null) -> void -Microsoft.IdentityModel.Tokens.TokenTypeValidationError._invalidTokenType -> string +Microsoft.IdentityModel.Tokens.TokenTypeValidationError.InvalidTokenType.get -> 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,7 +38,7 @@ Microsoft.IdentityModel.Tokens.ValidationResult.Result.get -> TResult override Microsoft.IdentityModel.Tokens.AlgorithmValidationError.GetException() -> System.Exception override Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.GetException() -> System.Exception override Microsoft.IdentityModel.Tokens.TokenTypeValidationError.GetException() -> System.Exception -static Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError +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/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/Results/Details/TokenTypeValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenTypeValidationError.cs index b97697e248..298abeefce 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, ValidationFailureType validationFailureType, @@ -20,7 +18,7 @@ internal TokenTypeValidationError( Exception? innerException = null) : base(messageDetail, validationFailureType, exceptionType, stackFrame, 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), + ValidationFailureType.NullArgument, + typeof(SecurityTokenArgumentNullException), + stackFrame, + null); // invalidTokenType + + protected string? InvalidTokenType { get; } } } #nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs index e512d2af5d..1f1dcef7af 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 the fact that the token type validation delegate threw an exception. + /// + public static readonly ValidationFailureType TokenTypeValidatorThrew = new TokenTypeValidationFailure("TokenTypeValidatorThrew"); } } diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs index fc685240d5..a95a60b363 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenType.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Diagnostics; using System.Linq; using Microsoft.IdentityModel.Logging; @@ -45,14 +44,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) { @@ -66,7 +65,7 @@ internal static ValidationResult ValidateTokenType( new MessageDetail(LogMessages.IDX10256), ValidationFailureType.TokenTypeValidationFailed, 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)) @@ -78,7 +77,7 @@ internal static ValidationResult ValidateTokenType( LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidTypes))), ValidationFailureType.TokenTypeValidationFailed, typeof(SecurityTokenInvalidTypeException), - new StackFrame(true), + ValidationError.GetCurrentStackFrame(), type); } 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..08bcd74b40 --- /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), + ValidationFailureType.TokenTypeValidationFailed, + typeof(SecurityTokenInvalidTypeException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 160), + "JWT") + }); + + // 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), + ValidationFailureType.TokenTypeValidationFailed, + typeof(CustomSecurityTokenInvalidTypeException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 175), + "JWT") + }); + + // 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), + ValidationFailureType.TokenTypeValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 205), + "JWT") + }); + + // 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), + CustomTokenTypeValidationError.CustomTokenTypeValidationFailureType, + typeof(CustomSecurityTokenInvalidTypeException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 190), + "JWT"), + }); + #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), + ValidationFailureType.TokenTypeValidationFailed, + typeof(SecurityTokenInvalidTypeException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 235), + "JWT") + }); + + // 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), + ValidationFailureType.TokenTypeValidationFailed, + typeof(CustomSecurityTokenInvalidTypeException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 259), + "JWT") + }); + + // 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), + ValidationFailureType.TokenTypeValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 274), + "JWT") + }); + + // 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), + ValidationFailureType.TokenTypeValidatorThrew, + typeof(SecurityTokenInvalidTypeException), + new StackFrame("JsonWebTokenHandler.ValidateToken.Internal.cs", 250), + "JWT", + 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 diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomTokenTypeValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomTokenTypeValidationDelegates.cs new file mode 100644 index 0000000000..ebc10c95e4 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomTokenTypeValidationDelegates.cs @@ -0,0 +1,141 @@ +// 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), + ValidationFailureType.TokenTypeValidationFailed, + 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), + ValidationFailureType.TokenTypeValidationFailed, + 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), + CustomTokenTypeValidationError.CustomTokenTypeValidationFailureType, + typeof(CustomSecurityTokenInvalidTypeException), + ValidationError.GetCurrentStackFrame(), + type); + } + + internal static ValidationResult CustomTokenTypeValidatorUnknownExceptionDelegate( + string? type, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomTokenTypeValidationError( + new MessageDetail(nameof(CustomTokenTypeValidatorUnknownExceptionDelegate), null), + ValidationFailureType.TokenTypeValidationFailed, + 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), + ValidationFailureType.TokenTypeValidationFailed, + 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), + ValidationFailureType.TokenTypeValidationFailed, + 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), + ValidationFailureType.TokenTypeValidationFailed, + 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 382a57a634..8f20be2168 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs @@ -156,6 +156,51 @@ 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, + ValidationFailureType validationFailureType, + Type exceptionType, + StackFrame stackFrame, + string? invalidTokenType, + Exception? innerException = null) + : base(messageDetail, validationFailureType, exceptionType, stackFrame, invalidTokenType, 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, + Exception? innerException = null) + : base(messageDetail, ValidationFailureType.TokenTypeValidationFailed, exceptionType, stackFrame, invalidTokenType, innerException) + { + } + } + #endregion // TokenTypeValidationErrors + // Other custom validation errors to be added here for signature validation, issuer signing key, etc. } #nullable restore