diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs index cfa1e5c057..1bb3e578ca 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs @@ -255,13 +255,28 @@ private async ValueTask> ValidateJWSAsync( DateTime? expires = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Exp) ? jsonWebToken.ValidTo : null; DateTime? notBefore = jsonWebToken.HasPayloadClaim(JwtRegisteredClaimNames.Nbf) ? jsonWebToken.ValidFrom : null; - ValidationResult lifetimeValidationResult = validationParameters.LifetimeValidator( - notBefore, expires, jsonWebToken, validationParameters, callContext); + ValidationResult lifetimeValidationResult; - if (!lifetimeValidationResult.IsValid) + try + { + lifetimeValidationResult = validationParameters.LifetimeValidator( + notBefore, expires, jsonWebToken, validationParameters, callContext); + + if (!lifetimeValidationResult.IsValid) + return lifetimeValidationResult.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 { - StackFrame lifetimeValidationFailureStackFrame = StackFrames.LifetimeValidationFailed ??= new StackFrame(true); - return lifetimeValidationResult.UnwrapError().AddStackFrame(lifetimeValidationFailureStackFrame); + return new LifetimeValidationError( + new MessageDetail(TokenLogMessages.IDX10271), + ValidationFailureType.LifetimeValidatorThrew, + typeof(SecurityTokenInvalidLifetimeException), + ValidationError.GetCurrentStackFrame(), + notBefore, + expires, + ex); } if (jsonWebToken.Audiences is not IList tokenAudiences) 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 ec438248be..d0572cd972 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs @@ -192,17 +192,32 @@ internal virtual ValidationResult ValidateConditions( StackFrames.AssertionConditionsNull); } - var lifetimeValidationResult = validationParameters.LifetimeValidator( - samlToken.Assertion.Conditions.NotBefore, - samlToken.Assertion.Conditions.NotOnOrAfter, - samlToken, - validationParameters, - callContext); - - if (!lifetimeValidationResult.IsValid) + ValidationResult lifetimeValidationResult; + + try { - StackFrames.LifetimeValidationFailed ??= new StackFrame(true); - return lifetimeValidationResult.UnwrapError().AddStackFrame(StackFrames.LifetimeValidationFailed); + lifetimeValidationResult = validationParameters.LifetimeValidator( + samlToken.Assertion.Conditions.NotBefore, + samlToken.Assertion.Conditions.NotOnOrAfter, + samlToken, + validationParameters, + callContext); + + if (!lifetimeValidationResult.IsValid) + return lifetimeValidationResult.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 LifetimeValidationError( + new MessageDetail(Tokens.LogMessages.IDX10271), + ValidationFailureType.LifetimeValidatorThrew, + typeof(SecurityTokenInvalidLifetimeException), + ValidationError.GetCurrentStackFrame(), + samlToken.Assertion.Conditions.NotBefore, + samlToken.Assertion.Conditions.NotOnOrAfter, + ex); } string? validatedAudience = null; 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 b2358e68ec..af3ee758ce 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs @@ -195,17 +195,32 @@ internal virtual ValidationResult ValidateConditions( StackFrames.AssertionConditionsNull); } - var lifetimeValidationResult = validationParameters.LifetimeValidator( - samlToken.Assertion.Conditions.NotBefore, - samlToken.Assertion.Conditions.NotOnOrAfter, - samlToken, - validationParameters, - callContext); + ValidationResult lifetimeValidationResult; - if (!lifetimeValidationResult.IsValid) + try { - StackFrames.LifetimeValidationFailed ??= new StackFrame(true); - return lifetimeValidationResult.UnwrapError().AddStackFrame(StackFrames.LifetimeValidationFailed); + lifetimeValidationResult = validationParameters.LifetimeValidator( + samlToken.Assertion.Conditions.NotBefore, + samlToken.Assertion.Conditions.NotOnOrAfter, + samlToken, + validationParameters, + callContext); + + if (!lifetimeValidationResult.IsValid) + return lifetimeValidationResult.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 LifetimeValidationError( + new MessageDetail(Tokens.LogMessages.IDX10271), + ValidationFailureType.LifetimeValidatorThrew, + typeof(SecurityTokenInvalidLifetimeException), + ValidationError.GetCurrentStackFrame(), + samlToken.Assertion.Conditions.NotBefore, + samlToken.Assertion.Conditions.NotOnOrAfter, + ex); } if (samlToken.Assertion.Conditions.OneTimeUse) diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt index cc4d32a13a..5a8febaf08 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -2,6 +2,7 @@ const Microsoft.IdentityModel.Tokens.LogMessages.IDX10002 = "IDX10002: Unknown e 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.IDX10270 = "IDX10270: AudienceValidationDelegate 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.IDX10272 = "IDX10272: SignatureValidationDelegate threw an exception, see inner exception." -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10273 = "IDX10273: AlgorithmValidationDelegate threw an exception, see inner exception." -> string const Microsoft.IdentityModel.Tokens.LogMessages.IDX10274 = "IDX10274: IssuerSigningKeyValidationDelegate threw an exception, see inner exception." -> string @@ -65,6 +66,7 @@ static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.AlgorithmVa static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.AudienceValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.IssuerSigningKeyValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.IssuerValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType +static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.LifetimeValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.NoTokenAudiencesProvided -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.NoValidationParameterAudiencesProvided -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.SignatureAlgorithmValidationFailed -> Microsoft.IdentityModel.Tokens.ValidationFailureType diff --git a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs index d87ce1e01c..0d10c18695 100644 --- a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs +++ b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs @@ -89,6 +89,7 @@ internal static class LogMessages 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 IDX10270 = "IDX10270: AudienceValidationDelegate threw an exception, see inner exception."; + public const string IDX10271 = "IDX10271: LifetimeValidationDelegate threw an exception, see inner exception."; public const string IDX10272 = "IDX10272: SignatureValidationDelegate threw an exception, see inner exception."; public const string IDX10273 = "IDX10273: AlgorithmValidationDelegate threw an exception, see inner exception."; public const string IDX10274 = "IDX10274: IssuerSigningKeyValidationDelegate threw an exception, see inner exception."; diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs index 5fc1fa5cc6..2556fea941 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs @@ -134,6 +134,11 @@ private class XmlValidationFailure : ValidationFailureType { internal XmlValidat /// public static readonly ValidationFailureType AlgorithmValidatorThrew = new AlgorithmValidationFailure("AlgorithmValidatorThrew"); + /// + /// Defines a type that represents the fact that the audience validation delegate threw an exception. + /// + public static readonly ValidationFailureType AudienceValidatorThrew = new AudienceValidationFailure("AudienceValidatorThrew"); + /// /// Defines a type that represents the fact that the issuer validation delegate threw an exception. /// @@ -141,9 +146,9 @@ private class XmlValidationFailure : ValidationFailureType { internal XmlValidat private class IssuerValidatorFailure : ValidationFailureType { internal IssuerValidatorFailure(string name) : base(name) { } } /// - /// Defines a type that represents the fact that the audience validation delegate threw an exception. + /// Defines a type that represents the fact that the lifetime validation delegate threw an exception. /// - public static readonly ValidationFailureType AudienceValidatorThrew = new AudienceValidationFailure("AudienceValidatorThrew"); + public static readonly ValidationFailureType LifetimeValidatorThrew = new LifetimeValidationFailure("LifetimeValidatorThrew"); /// /// Defines a type that represents the fact that the issuer signing key validation delegate threw an exception. diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Lifetime.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Lifetime.cs new file mode 100644 index 0000000000..9ce18c744d --- /dev/null +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Lifetime.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.JsonWebTokens.Extensibility.Tests +{ + public partial class JsonWebTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData( + nameof(GenerateLifetimeExtensibilityTestCases), + parameters: ["JWT", 2], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_LifetimeValidator_Extensibility( + LifetimeExtensibilityTheoryData theoryData) + { + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_LifetimeValidator_Extensibility)); + } + + public static TheoryData GenerateLifetimeExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) + { + return ExtensibilityTesting.GenerateLifetimeExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "JsonWebTokenHandler.ValidateToken.Internal.cs"); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomLifetimeValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomLifetimeValidationDelegates.cs new file mode 100644 index 0000000000..141eb4a584 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomLifetimeValidationDelegates.cs @@ -0,0 +1,159 @@ +// 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 CustomLifetimeValidationDelegates + { + internal static ValidationResult CustomLifetimeValidatorDelegate( + DateTime? notBefore, + DateTime? expires, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + // Returns a CustomLifetimeValidationError : LifetimeValidationError + return new CustomLifetimeValidationError( + new MessageDetail(nameof(CustomLifetimeValidatorDelegate), null), + ValidationFailureType.LifetimeValidationFailed, + typeof(SecurityTokenInvalidLifetimeException), + ValidationError.GetCurrentStackFrame(), + notBefore, + expires, + null); + } + + internal static ValidationResult CustomLifetimeValidatorCustomExceptionDelegate( + DateTime? notBefore, + DateTime? expires, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomLifetimeValidationError( + new MessageDetail(nameof(CustomLifetimeValidatorCustomExceptionDelegate), null), + ValidationFailureType.LifetimeValidationFailed, + typeof(CustomSecurityTokenInvalidLifetimeException), + ValidationError.GetCurrentStackFrame(), + notBefore, + expires, + null); + } + + internal static ValidationResult CustomLifetimeValidatorCustomExceptionCustomFailureTypeDelegate( + DateTime? notBefore, + DateTime? expires, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomLifetimeValidationError( + new MessageDetail(nameof(CustomLifetimeValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomLifetimeValidationError.CustomLifetimeValidationFailureType, + typeof(CustomSecurityTokenInvalidLifetimeException), + ValidationError.GetCurrentStackFrame(), + notBefore, + expires); + } + + internal static ValidationResult CustomLifetimeValidatorUnknownExceptionDelegate( + DateTime? notBefore, + DateTime? expires, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomLifetimeValidationError( + new MessageDetail(nameof(CustomLifetimeValidatorUnknownExceptionDelegate), null), + ValidationFailureType.LifetimeValidationFailed, + typeof(NotSupportedException), + ValidationError.GetCurrentStackFrame(), + notBefore, + expires, + null); + } + + internal static ValidationResult CustomLifetimeValidatorWithoutGetExceptionOverrideDelegate( + DateTime? notBefore, + DateTime? expires, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomLifetimeWithoutGetExceptionValidationOverrideError( + new MessageDetail(nameof(CustomLifetimeValidatorWithoutGetExceptionOverrideDelegate), null), + ValidationFailureType.LifetimeValidationFailed, + typeof(CustomSecurityTokenInvalidLifetimeException), + ValidationError.GetCurrentStackFrame(), + notBefore, + expires, + null); + } + + internal static ValidationResult LifetimeValidatorDelegate( + DateTime? notBefore, + DateTime? expires, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new LifetimeValidationError( + new MessageDetail(nameof(LifetimeValidatorDelegate), null), + ValidationFailureType.LifetimeValidationFailed, + typeof(SecurityTokenInvalidLifetimeException), + ValidationError.GetCurrentStackFrame(), + notBefore, + expires, + null); + } + + internal static ValidationResult LifetimeValidatorThrows( + DateTime? notBefore, + DateTime? expires, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + throw new CustomSecurityTokenInvalidLifetimeException(nameof(LifetimeValidatorThrows), null); + } + + internal static ValidationResult LifetimeValidatorCustomLifetimeExceptionTypeDelegate( + DateTime? notBefore, + DateTime? expires, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new LifetimeValidationError( + new MessageDetail(nameof(LifetimeValidatorCustomLifetimeExceptionTypeDelegate), null), + ValidationFailureType.LifetimeValidationFailed, + typeof(CustomSecurityTokenInvalidLifetimeException), + ValidationError.GetCurrentStackFrame(), + notBefore, + expires, + null); + } + + internal static ValidationResult LifetimeValidatorCustomExceptionTypeDelegate( + DateTime? notBefore, + DateTime? expires, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new LifetimeValidationError( + new MessageDetail(nameof(LifetimeValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.LifetimeValidationFailed, + typeof(CustomSecurityTokenException), + ValidationError.GetCurrentStackFrame(), + notBefore, + expires, + null); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/LifetimeExtensibilityTestCases.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/LifetimeExtensibilityTestCases.cs new file mode 100644 index 0000000000..24c92c20f5 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/LifetimeExtensibilityTestCases.cs @@ -0,0 +1,215 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; +using Xunit; +using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Logging; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests +{ + public partial class ExtensibilityTesting + { + public static TheoryData GenerateLifetimeExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames, + string stackFrameFileName) + { + TheoryData theoryData = new(); + CallContext callContext = new CallContext(); + DateTime utcNow = DateTime.UtcNow; + DateTime utcPlusOneHour = utcNow.AddHours(1); + + #region return CustomLifetimeValidationError + // Test cases where delegate is overridden and return a CustomLifetimeValidationError + // CustomLifetimeValidationError : LifetimeValidationError, ExceptionType: SecurityTokenInvalidLifetimeException + theoryData.Add(new LifetimeExtensibilityTheoryData( + "CustomLifetimeValidatorDelegate", + tokenHandlerType, + utcNow, + CustomLifetimeValidationDelegates.CustomLifetimeValidatorDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidLifetimeException), + nameof(CustomLifetimeValidationDelegates.CustomLifetimeValidatorDelegate)), + ValidationError = new CustomLifetimeValidationError( + new MessageDetail( + nameof(CustomLifetimeValidationDelegates.CustomLifetimeValidatorDelegate), null), + ValidationFailureType.LifetimeValidationFailed, + typeof(SecurityTokenInvalidLifetimeException), + new StackFrame("CustomLifetimeValidationDelegates.cs", 0), + utcNow, + utcPlusOneHour) + }); + + // CustomLifetimeValidationError : LifetimeValidationError, ExceptionType: CustomSecurityTokenInvalidLifetimeException : SecurityTokenInvalidLifetimeException + theoryData.Add(new LifetimeExtensibilityTheoryData( + "CustomLifetimeValidatorCustomExceptionDelegate", + tokenHandlerType, + utcNow, + CustomLifetimeValidationDelegates.CustomLifetimeValidatorCustomExceptionDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidLifetimeException), + nameof(CustomLifetimeValidationDelegates.CustomLifetimeValidatorCustomExceptionDelegate)), + ValidationError = new CustomLifetimeValidationError( + new MessageDetail( + nameof(CustomLifetimeValidationDelegates.CustomLifetimeValidatorCustomExceptionDelegate), null), + ValidationFailureType.LifetimeValidationFailed, + typeof(CustomSecurityTokenInvalidLifetimeException), + new StackFrame("CustomLifetimeValidationDelegates.cs", 0), + utcNow, + utcPlusOneHour), + }); + + // CustomLifetimeValidationError : LifetimeValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new LifetimeExtensibilityTheoryData( + "CustomLifetimeValidatorUnknownExceptionDelegate", + tokenHandlerType, + utcNow, + CustomLifetimeValidationDelegates.CustomLifetimeValidatorUnknownExceptionDelegate, + extraStackFrames: extraStackFrames) + { + // CustomLifetimeValidationError 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(CustomLifetimeValidationDelegates.CustomLifetimeValidatorUnknownExceptionDelegate))), + ValidationError = new CustomLifetimeValidationError( + new MessageDetail( + nameof(CustomLifetimeValidationDelegates.CustomLifetimeValidatorUnknownExceptionDelegate), null), + ValidationFailureType.LifetimeValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomLifetimeValidationDelegates.cs", 0), + utcNow, + utcPlusOneHour), + }); + + // CustomLifetimeValidationError : LifetimeValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new LifetimeExtensibilityTheoryData( + "CustomLifetimeValidatorCustomExceptionCustomFailureTypeDelegate", + tokenHandlerType, + utcNow, + CustomLifetimeValidationDelegates.CustomLifetimeValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidLifetimeException), + nameof(CustomLifetimeValidationDelegates.CustomLifetimeValidatorCustomExceptionCustomFailureTypeDelegate)), + ValidationError = new CustomLifetimeValidationError( + new MessageDetail( + nameof(CustomLifetimeValidationDelegates.CustomLifetimeValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomLifetimeValidationError.CustomLifetimeValidationFailureType, + typeof(CustomSecurityTokenInvalidLifetimeException), + new StackFrame("CustomLifetimeValidationDelegates.cs", 0), + utcNow, + utcPlusOneHour), + }); + #endregion + + #region return LifetimeValidationError + // Test cases where delegate is overridden and return an LifetimeValidationError + // LifetimeValidationError : ValidationError, ExceptionType: SecurityTokenInvalidLifetimeException + theoryData.Add(new LifetimeExtensibilityTheoryData( + "LifetimeValidatorDelegate", + tokenHandlerType, + utcNow, + CustomLifetimeValidationDelegates.LifetimeValidatorDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidLifetimeException), + nameof(CustomLifetimeValidationDelegates.LifetimeValidatorDelegate)), + ValidationError = new LifetimeValidationError( + new MessageDetail( + nameof(CustomLifetimeValidationDelegates.LifetimeValidatorDelegate), null), + ValidationFailureType.LifetimeValidationFailed, + typeof(SecurityTokenInvalidLifetimeException), + new StackFrame("CustomLifetimeValidationDelegates.cs", 0), + utcNow, + utcPlusOneHour) + }); + + // LifetimeValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidLifetimeException : SecurityTokenInvalidLifetimeException + theoryData.Add(new LifetimeExtensibilityTheoryData( + "LifetimeValidatorCustomLifetimeExceptionTypeDelegate", + tokenHandlerType, + utcNow, + CustomLifetimeValidationDelegates.LifetimeValidatorCustomLifetimeExceptionTypeDelegate, + extraStackFrames: extraStackFrames) + { + // LifetimeValidationError does not handle the exception type 'CustomSecurityTokenInvalidLifetimeException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidLifetimeException), + nameof(CustomLifetimeValidationDelegates.LifetimeValidatorCustomLifetimeExceptionTypeDelegate))), + ValidationError = new LifetimeValidationError( + new MessageDetail( + nameof(CustomLifetimeValidationDelegates.LifetimeValidatorCustomLifetimeExceptionTypeDelegate), null), + ValidationFailureType.LifetimeValidationFailed, + typeof(CustomSecurityTokenInvalidLifetimeException), + new StackFrame("CustomLifetimeValidationDelegates.cs", 0), + utcNow, + utcPlusOneHour) + }); + + // LifetimeValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new LifetimeExtensibilityTheoryData( + "LifetimeValidatorCustomExceptionTypeDelegate", + tokenHandlerType, + utcNow, + CustomLifetimeValidationDelegates.LifetimeValidatorCustomExceptionTypeDelegate, + extraStackFrames: extraStackFrames) + { + // LifetimeValidationError 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(CustomLifetimeValidationDelegates.LifetimeValidatorCustomExceptionTypeDelegate))), + ValidationError = new LifetimeValidationError( + new MessageDetail( + nameof(CustomLifetimeValidationDelegates.LifetimeValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.LifetimeValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomLifetimeValidationDelegates.cs", 0), + utcNow, + utcPlusOneHour) + }); + + // LifetimeValidationError : ValidationError, ExceptionType: SecurityTokenInvalidLifetimeException, inner: CustomSecurityTokenInvalidLifetimeException + theoryData.Add(new LifetimeExtensibilityTheoryData( + "LifetimeValidatorThrows", + tokenHandlerType, + utcNow, + CustomLifetimeValidationDelegates.LifetimeValidatorThrows, + extraStackFrames: extraStackFrames - 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidLifetimeException), + string.Format(Tokens.LogMessages.IDX10271), + typeof(CustomSecurityTokenInvalidLifetimeException)), + ValidationError = new LifetimeValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10271), null), + ValidationFailureType.LifetimeValidatorThrew, + typeof(SecurityTokenInvalidLifetimeException), + new StackFrame(stackFrameFileName, 0), + utcNow, + utcPlusOneHour, + new SecurityTokenInvalidLifetimeException(nameof(CustomLifetimeValidationDelegates.LifetimeValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/LifetimeExtensibilityTheoryData.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/LifetimeExtensibilityTheoryData.cs new file mode 100644 index 0000000000..f4e209f8e9 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/LifetimeExtensibilityTheoryData.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.IdentityModel.Tokens; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests +{ + public class LifetimeExtensibilityTheoryData : ExtensibilityTheoryData + { + internal LifetimeExtensibilityTheoryData( + string testId, + string tokenHandlerType, + DateTime utcNow, + LifetimeValidationDelegate lifetimeValidationDelegate, + int extraStackFrames) : base(testId, tokenHandlerType, extraStackFrames) + { + SecurityTokenDescriptor = new() + { + Issuer = Default.Issuer, + IssuedAt = utcNow.AddHours(-1), + NotBefore = utcNow, + Expires = utcNow.AddHours(1), + }; + + ValidationParameters.LifetimeValidator = lifetimeValidationDelegate; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Lifetime.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Lifetime.cs new file mode 100644 index 0000000000..09a905e2a5 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Lifetime.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml2.Extensibility.Tests +{ + public partial class Saml2SecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData( + nameof(GenerateLifetimeExtensibilityTestCases), + parameters: ["SAML2", 2], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_LifetimeValidator_Extensibility( + LifetimeExtensibilityTheoryData theoryData) + { + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_LifetimeValidator_Extensibility)); + } + + public static TheoryData GenerateLifetimeExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) + { + return ExtensibilityTesting.GenerateLifetimeExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "Saml2SecurityTokenHandler.ValidateToken.Internal.cs"); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Lifetime.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Lifetime.cs new file mode 100644 index 0000000000..8dbe848283 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Lifetime.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml.Extensibility.Tests +{ + public partial class SamlSecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData( + nameof(GenerateLifetimeExtensibilityTestCases), + parameters: ["SAML", 2], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_LifetimeValidator_Extensibility( + LifetimeExtensibilityTheoryData theoryData) + { + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_LifetimeValidator_Extensibility)); + } + + public static TheoryData GenerateLifetimeExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) + { + return ExtensibilityTesting.GenerateLifetimeExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "SamlSecurityTokenHandler.ValidateToken.Internal.cs"); + } + } +} +#nullable restore