From 61507491c0b3898b7bfd8156d0f8181b395379d5 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Fri, 22 Nov 2024 10:29:28 +0000 Subject: [PATCH 01/23] SAML and SAML2 new model validation: Token Replay (#2994) * Updated SAML and SAML2 token replay validation and OneTimeCondition to match existing behaviour. Added comparison tests between new validation model and the legacy one * Commented log message no longer in use given we leave the lifetime validation for after the token is created * Fixed test failure due to wrong merge --- .../InternalAPI.Unshipped.txt | 4 +- ...rityTokenHandler.ValidateToken.Internal.cs | 23 +- ...yTokenHandler.ValidateToken.StackFrames.cs | 1 - .../Saml2/LogMessages.cs | 2 +- .../Saml2/Saml2Conditions.cs | 14 +- ...rityTokenHandler.ValidateToken.Internal.cs | 48 ++-- ...sts.ValidateTokenAsyncTests.TokenReplay.cs | 206 ++++++++++++++++++ ...sts.ValidateTokenAsyncTests.TokenReplay.cs | 188 ++++++++++++++++ 8 files changed, 439 insertions(+), 47 deletions(-) create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.TokenReplay.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.TokenReplay.cs diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt index 40f6b57bad..db8578df07 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt @@ -19,7 +19,6 @@ Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.Saml2ValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException = null) -> void override Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.GetException() -> System.Exception override Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.GetException() -> System.Exception -static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.IssuerSigningKeyValidationFailed -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.IssuerValidationFailed -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.SignatureValidationFailed -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateSignature(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult @@ -45,6 +44,7 @@ static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrame static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.TokenNull -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.TokenValidationParametersNull -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateSignature(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult -virtual Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ReadSaml2Token(string token, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult virtual Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ReadSamlToken(string token, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult virtual Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateConditions(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult +virtual Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ReadSaml2Token(string token, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult +virtual Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateOneTimeUseCondition(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationError 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 7d45db493a..24b0cea3ba 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Diagnostics; using System.Linq; using System.Threading; @@ -60,10 +61,7 @@ internal async Task> ValidateTokenAsync( ValidationResult conditionsResult = ValidateConditions(samlToken, validationParameters, callContext); if (!conditionsResult.IsValid) - { - StackFrames.AssertionConditionsValidationFailed ??= new StackFrame(true); - return conditionsResult.UnwrapError().AddStackFrame(StackFrames.AssertionConditionsValidationFailed); - } + return conditionsResult.UnwrapError().AddCurrentStackFrame(); ValidationResult issuerValidationResult = await validationParameters.IssuerValidatorAsync( samlToken.Issuer, @@ -78,6 +76,18 @@ internal async Task> ValidateTokenAsync( return issuerValidationResult.UnwrapError().AddStackFrame(StackFrames.IssuerValidationFailed); } + if (samlToken.Assertion.Conditions is not null) + { + ValidationResult tokenReplayValidationResult = Validators.ValidateTokenReplay( + samlToken.Assertion.Conditions.NotOnOrAfter, + samlToken.Assertion.CanonicalString, + validationParameters, + callContext); + + if (!tokenReplayValidationResult.IsValid) + return tokenReplayValidationResult.UnwrapError().AddCurrentStackFrame(); + } + ValidationResult signatureValidationResult = ValidateSignature(samlToken, validationParameters, callContext); if (!signatureValidationResult.IsValid) @@ -94,10 +104,7 @@ internal async Task> ValidateTokenAsync( callContext); if (!issuerSigningKeyValidationResult.IsValid) - { - StackFrames.IssuerSigningKeyValidationFailed ??= new StackFrame(true); - return issuerSigningKeyValidationResult.UnwrapError().AddStackFrame(StackFrames.IssuerSigningKeyValidationFailed); - } + return issuerSigningKeyValidationResult.UnwrapError().AddCurrentStackFrame(); return new ValidatedToken(samlToken, this, validationParameters); } diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.StackFrames.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.StackFrames.cs index 2108a5c662..c7a05acfc0 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.StackFrames.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.StackFrames.cs @@ -15,7 +15,6 @@ internal static class StackFrames internal static StackFrame? TokenNull; internal static StackFrame? TokenValidationParametersNull; internal static StackFrame? IssuerValidationFailed; - internal static StackFrame? IssuerSigningKeyValidationFailed; internal static StackFrame? SignatureValidationFailed; // Stack frames from ValidateConditions diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/LogMessages.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/LogMessages.cs index a0e71cf811..d60b4b4a5a 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/LogMessages.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/LogMessages.cs @@ -24,7 +24,7 @@ internal static class LogMessages internal const string IDX13511 = "IDX13511: The Saml2SecurityToken cannot be validated because the Assertion specifies a ProxyRestriction condition.Enforcement of the ProxyRestriction condition is not supported by default. To customize the enforcement of Saml2Conditions, extend Saml2SecurityTokenHandler and override ValidateConditions."; internal const string IDX13512 = "IDX13512: Unable to validate token. A Saml2SamlAttributeStatement can only have one Saml2Attribute of type 'Actor'. This special Saml2Attribute is used in delegation scenarios."; internal const string IDX13513 = "IDX13513: NotBefore '{0}', is after NotOnOrAfter '{1}'."; - internal const string IDX13514 = "IDX13514: NotOnOrAfter '{0}', is before NotBefore '{1}'."; + //internal const string IDX13514 = "IDX13514: NotOnOrAfter '{0}', is before NotBefore '{1}'."; internal const string IDX13515 = "IDX13515: SamlId value threw on XmlConvert.VerifyNCName. value: '{0}'"; internal const string IDX13516 = "IDX13516: A Saml2Statement of type: '{0}' was found when ProcessingStatements and creating the ClaimsIdentity. These claims have been skipped. If you need to process this Statement, you will need to derive a custom Saml2SecurityTokenHandler and override ProcessStatements."; diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2Conditions.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2Conditions.cs index c339042d4c..f7983af30a 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2Conditions.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2Conditions.cs @@ -78,19 +78,7 @@ public DateTime? NotBefore public DateTime? NotOnOrAfter { get { return _notOnOrAfter; } - set - { - value = DateTimeUtil.ToUniversalTime(value); - - //This should not be checked here, instead fail during validation of the token. Will remove this code once we release new validation model bug #2905 - /* if (value != null && NotBefore.HasValue) - { - if (value.Value <= NotBefore.Value) - throw LogExceptionMessage(new ArgumentException(FormatInvariant(LogMessages.IDX13514, MarkAsNonPII(value), MarkAsNonPII(NotBefore)))); - }*/ - - _notOnOrAfter = value; - } + set { _notOnOrAfter = DateTimeUtil.ToUniversalTime(value); } } /// 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 2ff1a01def..f60c3817ad 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; @@ -64,10 +65,7 @@ internal async Task> ValidateTokenAsync( callContext); if (!conditionsResult.IsValid) - { - StackFrames.AssertionConditionsValidationFailed ??= new StackFrame(true); - return conditionsResult.UnwrapError().AddStackFrame(StackFrames.AssertionConditionsValidationFailed); - } + return conditionsResult.UnwrapError().AddCurrentStackFrame(); ValidationResult validatedIssuerResult = await validationParameters.IssuerValidatorAsync( samlToken.Issuer, @@ -82,6 +80,18 @@ internal async Task> ValidateTokenAsync( return validatedIssuerResult.UnwrapError().AddStackFrame(StackFrames.IssuerValidationFailed); } + if (samlToken.Assertion.Conditions is not null) + { + ValidationResult tokenReplayValidationResult = Validators.ValidateTokenReplay( + samlToken.Assertion.Conditions.NotOnOrAfter, + samlToken.Assertion.CanonicalString, + validationParameters, + callContext); + + if (!tokenReplayValidationResult.IsValid) + return tokenReplayValidationResult.UnwrapError().AddCurrentStackFrame(); + } + var signatureValidationResult = ValidateSignature(samlToken, validationParameters, callContext); if (!signatureValidationResult.IsValid) { @@ -144,33 +154,21 @@ internal virtual ValidationResult ValidateConditions( if (samlToken.Assertion.Conditions.OneTimeUse) { - //ValidateOneTimeUseCondition(samlToken, validationParameters); - // We can keep an overridable method for this, or rely on the TokenReplayValidator delegate. - var oneTimeUseValidationResult = validationParameters.TokenReplayValidator( - samlToken.Assertion.Conditions.NotOnOrAfter, - samlToken.Assertion.CanonicalString, - validationParameters, - callContext); + var oneTimeUseValidationError = ValidateOneTimeUseCondition(samlToken, validationParameters, callContext); - if (!oneTimeUseValidationResult.IsValid) - { - StackFrames.OneTimeUseValidationFailed ??= new StackFrame(true); - return oneTimeUseValidationResult.UnwrapError().AddStackFrame(StackFrames.OneTimeUseValidationFailed); - } + if (oneTimeUseValidationError is not null) + return oneTimeUseValidationError.AddCurrentStackFrame(); } - if (samlToken.Assertion.Conditions.ProxyRestriction != null) + if (samlToken.Assertion.Conditions.ProxyRestriction is not null) { - //throw LogExceptionMessage(new SecurityTokenValidationException(LogMessages.IDX13511)); var proxyValidationError = ValidateProxyRestriction( samlToken, validationParameters, callContext); if (proxyValidationError is not null) - { - return proxyValidationError; - } + return proxyValidationError.AddCurrentStackFrame(); } string? validatedAudience = null; @@ -203,7 +201,13 @@ internal virtual ValidationResult ValidateConditions( internal virtual ValidationError? ValidateProxyRestriction(Saml2SecurityToken samlToken, ValidationParameters validationParameters, CallContext callContext) #pragma warning restore CA1801 // Review unused parameters { - // return an error, or ignore and allow overriding? + return null; + } + +#pragma warning disable CA1801 // Review unused parameters + internal virtual ValidationError? ValidateOneTimeUseCondition(Saml2SecurityToken samlToken, ValidationParameters validationParameters, CallContext callContext) +#pragma warning restore CA1801 // Review unused parameters + { return null; } } diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.TokenReplay.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.TokenReplay.cs new file mode 100644 index 0000000000..098dde0650 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.TokenReplay.cs @@ -0,0 +1,206 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.TestUtils; +using Microsoft.IdentityModel.Tokens.Saml2; +using Xunit; + +namespace Microsoft.IdentityModel.Tokens.Saml.Tests +{ +#nullable enable + public partial class Saml2SecurityTokenHandlerTests + { + [Theory, MemberData(nameof(ValidateTokenAsync_TokenReplay_TestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_TokenReplayComparison(ValidateTokenAsyncTokenReplayTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.ValidateTokenAsync_TokenReplayComparison", theoryData); + + Saml2SecurityTokenHandler saml2TokenHandler = new Saml2SecurityTokenHandler(); + + Saml2SecurityToken saml2Token = CreateTokenForTokenReplayValidation(theoryData.TokenHasExpiration); + + // Validate the token using TokenValidationParameters + TokenValidationResult tokenValidationResult = + await saml2TokenHandler.ValidateTokenAsync(saml2Token.Assertion.CanonicalString, theoryData.TokenValidationParameters); + + // Validate the token using ValidationParameters. + ValidationResult validationResult = + await saml2TokenHandler.ValidateTokenAsync( + saml2Token, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + // Ensure the validity of the results match the expected result. + if (tokenValidationResult.IsValid != theoryData.ExpectedIsValid) + context.AddDiff($"tokenValidationResult.IsValid != theoryData.ExpectedIsValid"); + + if (validationResult.IsValid != theoryData.ExpectedIsValid) + context.AddDiff($"validationResult.IsValid != theoryData.ExpectedIsValid"); + + if (!theoryData.ExpectedIsValid) + { + // Verify the exception provided by both paths match. + var tokenValidationResultException = tokenValidationResult.Exception; + var validationResultException = validationResult.UnwrapError().GetException(); + + theoryData.ExpectedException.ProcessException(tokenValidationResultException, context); + theoryData.ExpectedExceptionValidationParameters!.ProcessException(validationResultException, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData ValidateTokenAsync_TokenReplay_TestCases + { + get + { + var successfulTokenReplayCache = new TokenReplayCache + { + OnAddReturnValue = true, + OnFindReturnValue = false, + }; + + var failToAddTokenReplayCache = new TokenReplayCache + { + OnAddReturnValue = false, + OnFindReturnValue = false, + }; + + var tokenAlreadySavedTokenReplayCache = new TokenReplayCache + { + OnAddReturnValue = true, + OnFindReturnValue = true, + }; + + var theoryData = new TheoryData(); + + theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Valid_TokenHasNotBeenReplayed") + { + TokenValidationParameters = CreateTokenValidationParameters(successfulTokenReplayCache), + ValidationParameters = CreateValidationParameters(successfulTokenReplayCache), + }); + + theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Valid_TokenHasNoExpiration_TokenReplayCacheIsNull") + { + TokenHasExpiration = false, + TokenValidationParameters = CreateTokenValidationParameters(null), + ValidationParameters = CreateValidationParameters(null), + }); + + theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Invalid_TokenHasNoExpiration_TokenReplayCacheIsNotNull") + { + TokenHasExpiration = false, + TokenValidationParameters = CreateTokenValidationParameters(successfulTokenReplayCache), + ValidationParameters = CreateValidationParameters(successfulTokenReplayCache), + ExpectedIsValid = false, + ExpectedException = ExpectedException.SecurityTokenNoExpirationException("IDX10227:"), + ExpectedExceptionValidationParameters = ExpectedException.SecurityTokenNoExpirationException("IDX10227:"), + }); + + theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Invalid_TokenCouldNotBeAdded") + { + TokenValidationParameters = CreateTokenValidationParameters(failToAddTokenReplayCache), + ValidationParameters = CreateValidationParameters(failToAddTokenReplayCache), + ExpectedIsValid = false, + ExpectedException = ExpectedException.SecurityTokenReplayAddFailedException("IDX10229:"), + ExpectedExceptionValidationParameters = ExpectedException.SecurityTokenReplayAddFailedException("IDX10229:"), + }); + + theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Invalid_TokenHasBeenReplayed") + { + TokenValidationParameters = CreateTokenValidationParameters(tokenAlreadySavedTokenReplayCache), + ValidationParameters = CreateValidationParameters(tokenAlreadySavedTokenReplayCache), + ExpectedIsValid = false, + ExpectedException = ExpectedException.SecurityTokenReplayDetectedException("IDX10228:"), + ExpectedExceptionValidationParameters = ExpectedException.SecurityTokenReplayDetectedException("IDX10228:"), + }); + + return theoryData; + + static TokenValidationParameters CreateTokenValidationParameters(ITokenReplayCache? tokenReplayCache) + { + var tokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false, + ValidateIssuer = false, + ValidateLifetime = false, + ValidateTokenReplay = true, + ValidateIssuerSigningKey = false, + RequireSignedTokens = false, + TokenReplayCache = tokenReplayCache + }; + + return tokenValidationParameters; + } + + static ValidationParameters CreateValidationParameters(ITokenReplayCache? tokenReplayCache) + { + ValidationParameters validationParameters = new ValidationParameters(); + validationParameters.TokenReplayCache = tokenReplayCache; + + validationParameters.AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation; + validationParameters.AudienceValidator = SkipValidationDelegates.SkipAudienceValidation; + validationParameters.IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation; + validationParameters.IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation; + validationParameters.LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation; + validationParameters.SignatureValidator = SkipValidationDelegates.SkipSignatureValidation; + validationParameters.TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation; + + return validationParameters; + } + } + } + + public class ValidateTokenAsyncTokenReplayTheoryData : TheoryDataBase + { + public ValidateTokenAsyncTokenReplayTheoryData(string testId) : base(testId) { } + + internal ExpectedException? ExpectedExceptionValidationParameters { get; set; } = ExpectedException.NoExceptionExpected; + + internal bool TokenHasExpiration { get; set; } = true; + + internal bool ExpectedIsValid { get; set; } = true; + + internal ValidationParameters? ValidationParameters { get; set; } + + internal TokenValidationParameters? TokenValidationParameters { get; set; } + } + + private static Saml2SecurityToken CreateTokenForTokenReplayValidation(bool hasExpiration = true) + { + Saml2SecurityTokenHandler saml2SecurityTokenHandler = new Saml2SecurityTokenHandler(); + // If the token has expiration, we use the default times. + saml2SecurityTokenHandler.SetDefaultTimesOnTokenCreation = hasExpiration; + + SecurityTokenDescriptor securityTokenDescriptor; + + if (!hasExpiration) + { + securityTokenDescriptor = new SecurityTokenDescriptor + { + Subject = Default.ClaimsIdentity, + Issuer = Default.Issuer, + Audience = Default.Audience, + Expires = null, + NotBefore = null, + IssuedAt = null, + }; + } + else + { + securityTokenDescriptor = new SecurityTokenDescriptor + { + Subject = Default.ClaimsIdentity, + Issuer = Default.Issuer, + Audience = Default.Audience, + }; + } + + return (Saml2SecurityToken)saml2SecurityTokenHandler.CreateToken(securityTokenDescriptor); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.TokenReplay.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.TokenReplay.cs new file mode 100644 index 0000000000..0ae2a2ed76 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.TokenReplay.cs @@ -0,0 +1,188 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.TestUtils; +using Xunit; + +namespace Microsoft.IdentityModel.Tokens.Saml.Tests +{ +#nullable enable + public partial class SamlSecurityTokenHandlerTests + { + [Theory, MemberData(nameof(ValidateTokenAsync_TokenReplay_TestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_TokenReplayComparison(ValidateTokenAsyncTokenReplayTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.ValidateTokenAsync_TokenReplayComparison", theoryData); + + SamlSecurityTokenHandler samlTokenHandler = new SamlSecurityTokenHandler(); + + SamlSecurityToken samlToken = CreateTokenForTokenReplayValidation(theoryData.TokenHasExpiration); + + // Validate the token using TokenValidationParameters + TokenValidationResult tokenValidationResult = + await samlTokenHandler.ValidateTokenAsync(samlToken.Assertion.CanonicalString, theoryData.TokenValidationParameters); + + // Validate the token using ValidationParameters. + ValidationResult validationResult = + await samlTokenHandler.ValidateTokenAsync( + samlToken, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + // Ensure the validity of the results match the expected result. + if (tokenValidationResult.IsValid != theoryData.ExpectedIsValid) + context.AddDiff($"tokenValidationResult.IsValid != theoryData.ExpectedIsValid"); + + if (validationResult.IsValid != theoryData.ExpectedIsValid) + context.AddDiff($"validationResult.IsValid != theoryData.ExpectedIsValid"); + + if (!theoryData.ExpectedIsValid) + { + // Verify the exception provided by both paths match. + var tokenValidationResultException = tokenValidationResult.Exception; + var validationResultException = validationResult.UnwrapError().GetException(); + + theoryData.ExpectedException.ProcessException(tokenValidationResultException, context); + theoryData.ExpectedExceptionValidationParameters!.ProcessException(validationResultException, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData ValidateTokenAsync_TokenReplay_TestCases + { + get + { + var successfulTokenReplayCache = new TokenReplayCache + { + OnAddReturnValue = true, + OnFindReturnValue = false, + }; + + var failToAddTokenReplayCache = new TokenReplayCache + { + OnAddReturnValue = false, + OnFindReturnValue = false, + }; + + var tokenAlreadySavedTokenReplayCache = new TokenReplayCache + { + OnAddReturnValue = true, + OnFindReturnValue = true, + }; + + var theoryData = new TheoryData(); + + theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Valid_TokenHasNotBeenReplayed") + { + TokenValidationParameters = CreateTokenValidationParameters(successfulTokenReplayCache), + ValidationParameters = CreateValidationParameters(successfulTokenReplayCache), + }); + + theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Invalid_TokenCouldNotBeAdded") + { + TokenValidationParameters = CreateTokenValidationParameters(failToAddTokenReplayCache), + ValidationParameters = CreateValidationParameters(failToAddTokenReplayCache), + ExpectedIsValid = false, + ExpectedException = ExpectedException.SecurityTokenReplayAddFailedException("IDX10229:"), + ExpectedExceptionValidationParameters = ExpectedException.SecurityTokenReplayAddFailedException("IDX10229:"), + }); + + theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Invalid_TokenHasBeenReplayed") + { + TokenValidationParameters = CreateTokenValidationParameters(tokenAlreadySavedTokenReplayCache), + ValidationParameters = CreateValidationParameters(tokenAlreadySavedTokenReplayCache), + ExpectedIsValid = false, + ExpectedException = ExpectedException.SecurityTokenReplayDetectedException("IDX10228:"), + ExpectedExceptionValidationParameters = ExpectedException.SecurityTokenReplayDetectedException("IDX10228:"), + }); + + return theoryData; + + static TokenValidationParameters CreateTokenValidationParameters(ITokenReplayCache? tokenReplayCache) + { + var tokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false, + ValidateIssuer = false, + ValidateLifetime = false, + ValidateTokenReplay = true, + ValidateIssuerSigningKey = false, + RequireSignedTokens = false, + TokenReplayCache = tokenReplayCache + }; + + return tokenValidationParameters; + } + + static ValidationParameters CreateValidationParameters(ITokenReplayCache? tokenReplayCache) + { + ValidationParameters validationParameters = new ValidationParameters(); + validationParameters.TokenReplayCache = tokenReplayCache; + + validationParameters.AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation; + validationParameters.AudienceValidator = SkipValidationDelegates.SkipAudienceValidation; + validationParameters.IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation; + validationParameters.IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation; + validationParameters.LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation; + validationParameters.SignatureValidator = SkipValidationDelegates.SkipSignatureValidation; + validationParameters.TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation; + + return validationParameters; + } + } + } + + public class ValidateTokenAsyncTokenReplayTheoryData : TheoryDataBase + { + public ValidateTokenAsyncTokenReplayTheoryData(string testId) : base(testId) { } + + internal ExpectedException? ExpectedExceptionValidationParameters { get; set; } = ExpectedException.NoExceptionExpected; + + internal bool TokenHasExpiration { get; set; } = true; + + internal bool ExpectedIsValid { get; set; } = true; + + internal ValidationParameters? ValidationParameters { get; set; } + + internal TokenValidationParameters? TokenValidationParameters { get; set; } + } + + private static SamlSecurityToken CreateTokenForTokenReplayValidation(bool hasExpiration = true) + { + SamlSecurityTokenHandler samlSecurityTokenHandler = new SamlSecurityTokenHandler(); + // If the token has expiration, we use the default times. + samlSecurityTokenHandler.SetDefaultTimesOnTokenCreation = hasExpiration; + + SecurityTokenDescriptor securityTokenDescriptor; + + if (!hasExpiration) + { + securityTokenDescriptor = new SecurityTokenDescriptor + { + Subject = Default.SamlClaimsIdentity, + Issuer = Default.Issuer, + Audience = Default.Audience, + Expires = null, + NotBefore = null, + IssuedAt = null, + }; + } + else + { + securityTokenDescriptor = new SecurityTokenDescriptor + { + Subject = Default.SamlClaimsIdentity, + Issuer = Default.Issuer, + Audience = Default.Audience, + }; + } + + return (SamlSecurityToken)samlSecurityTokenHandler.CreateToken(securityTokenDescriptor); + } + } +} +#nullable restore From 9463f20e0e5e173e7904351af187d4529ecb2147 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Mon, 25 Nov 2024 11:35:05 +0000 Subject: [PATCH 02/23] 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 From d38c8902709205893e1156c4e0737aa995ec2e0f Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Mon, 25 Nov 2024 13:53:20 +0000 Subject: [PATCH 03/23] Extensibility tests: Issuer - SAML and SAML2 (#3026) * Rename JWT issuer extensibility tests to better align on the solution explorer. Simplified creation of stack frames for comparison (cherry picked from commit 4ed421da86af428a6f9fd6b7d94a48d9e5c0980f) * Handle potential exceptions thrown by the issuer validator in SAML and SAML2 (cherry picked from commit 9188432e08bec1c90b045d326e8c936ed2e8a0f4) * Added extensibility tests for issuer validation on SAML and SAML2 (cherry picked from commit 91a8b1d4d0db0b0f28b941b83a07140f5ffa2d89) * Updated validation failure type position in tests --- ...rityTokenHandler.ValidateToken.Internal.cs | 30 +- ...rityTokenHandler.ValidateToken.Internal.cs | 30 +- ...onWebTokenHandler.Extensibility.Issuer.cs} | 100 +++---- ...curityTokenHandler.Extensibility.Issuer.cs | 278 ++++++++++++++++++ ...curityTokenHandler.Extensibility.Issuer.cs | 278 ++++++++++++++++++ 5 files changed, 637 insertions(+), 79 deletions(-) rename test/Microsoft.IdentityModel.JsonWebTokens.Tests/{JsonWebTokenHandler.Issuer.Extensibility.cs => JsonWebTokenHandler.Extensibility.Issuer.cs} (82%) create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Issuer.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Issuer.cs 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 24b0cea3ba..2a501e2705 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs @@ -63,17 +63,29 @@ internal async Task> ValidateTokenAsync( if (!conditionsResult.IsValid) return conditionsResult.UnwrapError().AddCurrentStackFrame(); - ValidationResult issuerValidationResult = await validationParameters.IssuerValidatorAsync( - samlToken.Issuer, - samlToken, - validationParameters, - callContext, - cancellationToken).ConfigureAwait(false); + try + { + ValidationResult issuerValidationResult = await validationParameters.IssuerValidatorAsync( + samlToken.Issuer, + samlToken, + validationParameters, + callContext, + cancellationToken).ConfigureAwait(false); - if (!issuerValidationResult.IsValid) + if (!issuerValidationResult.IsValid) + return issuerValidationResult.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 { - StackFrames.IssuerValidationFailed ??= new StackFrame(true); - return issuerValidationResult.UnwrapError().AddStackFrame(StackFrames.IssuerValidationFailed); + return new IssuerValidationError( + new MessageDetail(Tokens.LogMessages.IDX10269), + ValidationFailureType.IssuerValidatorThrew, + typeof(SecurityTokenInvalidIssuerException), + ValidationError.GetCurrentStackFrame(), + samlToken.Issuer, + ex); } if (samlToken.Assertion.Conditions is not 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 f60c3817ad..8804d4d6ad 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs @@ -67,17 +67,29 @@ internal async Task> ValidateTokenAsync( if (!conditionsResult.IsValid) return conditionsResult.UnwrapError().AddCurrentStackFrame(); - ValidationResult validatedIssuerResult = await validationParameters.IssuerValidatorAsync( - samlToken.Issuer, - samlToken, - validationParameters, - callContext, - cancellationToken).ConfigureAwait(false); + try + { + ValidationResult issuerValidationResult = await validationParameters.IssuerValidatorAsync( + samlToken.Issuer, + samlToken, + validationParameters, + callContext, + cancellationToken).ConfigureAwait(false); - if (!validatedIssuerResult.IsValid) + if (!issuerValidationResult.IsValid) + return issuerValidationResult.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 { - StackFrames.IssuerValidationFailed ??= new StackFrame(true); - return validatedIssuerResult.UnwrapError().AddStackFrame(StackFrames.IssuerValidationFailed); + return new IssuerValidationError( + new MessageDetail(Tokens.LogMessages.IDX10269), + ValidationFailureType.IssuerValidatorThrew, + typeof(SecurityTokenInvalidIssuerException), + ValidationError.GetCurrentStackFrame(), + samlToken.Issuer, + ex); } if (samlToken.Assertion.Conditions is not null) diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Issuer.Extensibility.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Issuer.cs similarity index 82% rename from test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Issuer.Extensibility.cs rename to test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Issuer.cs index bb14541c7d..7f703bb951 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Issuer.Extensibility.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Issuer.cs @@ -2,11 +2,11 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; 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; @@ -22,8 +22,8 @@ public async Task ValidateTokenAsync_IssuerValidator_Extensibility(IssuerExtensi { var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_IssuerValidator_Extensibility)}", theoryData); context.IgnoreType = false; - for (int i = 1; i < theoryData.StackFrames.Count; i++) - theoryData.IssuerValidationError!.AddStackFrame(theoryData.StackFrames[i]); + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.IssuerValidationError!.AddStackFrame(new StackFrame(false)); try { @@ -69,11 +69,7 @@ public static TheoryData Issuer_ExtensibilityTest "CustomIssuerValidatorDelegate", issuerGuid, CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync, - [ - new StackFrame("CustomIssuerValidationDelegates", 88), - new StackFrame(false), - new StackFrame(false) - ]) + extraStackFrames: 2) { ExpectedException = new ExpectedException( typeof(SecurityTokenInvalidIssuerException), @@ -83,7 +79,7 @@ public static TheoryData Issuer_ExtensibilityTest nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync), null), ValidationFailureType.IssuerValidationFailed, typeof(SecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates", 88), + new StackFrame("CustomIssuerValidationDelegates.cs", 88), issuerGuid) }); @@ -92,11 +88,7 @@ public static TheoryData Issuer_ExtensibilityTest "CustomIssuerValidatorCustomExceptionDelegate", issuerGuid, CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync, - [ - new StackFrame("CustomIssuerValidationDelegates", 107), - new StackFrame(false), - new StackFrame(false) - ]) + extraStackFrames: 2) { ExpectedException = new ExpectedException( typeof(CustomSecurityTokenInvalidIssuerException), @@ -106,7 +98,7 @@ public static TheoryData Issuer_ExtensibilityTest nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync), null), ValidationFailureType.IssuerValidationFailed, typeof(CustomSecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates", 107), + new StackFrame("CustomIssuerValidationDelegates.cs", 107), issuerGuid), }); @@ -115,21 +107,20 @@ public static TheoryData Issuer_ExtensibilityTest "CustomIssuerValidatorUnknownExceptionDelegate", issuerGuid, CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync, - [ - new StackFrame("CustomIssuerValidationDelegates", 139), - new StackFrame(false), - new StackFrame(false) - ]) + extraStackFrames: 2) { - ExpectedException = new ExpectedException( - typeof(SecurityTokenException), - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync)), + // CustomIssuerValidationError 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(CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync))), IssuerValidationError = new CustomIssuerValidationError( new MessageDetail( nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync), null), ValidationFailureType.IssuerValidationFailed, typeof(NotSupportedException), - new StackFrame("CustomIssuerValidationDelegates", 139), + new StackFrame("CustomIssuerValidationDelegates.cs", 139), issuerGuid), }); @@ -138,11 +129,7 @@ public static TheoryData Issuer_ExtensibilityTest "CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegate", issuerGuid, CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync, - [ - new StackFrame("CustomIssuerValidationDelegates", 123), - new StackFrame(false), - new StackFrame(false) - ]) + extraStackFrames: 2) { ExpectedException = new ExpectedException( typeof(CustomSecurityTokenInvalidIssuerException), @@ -152,7 +139,7 @@ public static TheoryData Issuer_ExtensibilityTest nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync), null), CustomIssuerValidationError.CustomIssuerValidationFailureType, typeof(CustomSecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates", 123), + new StackFrame("CustomIssuerValidationDelegates.cs", 123), issuerGuid, null), }); @@ -165,11 +152,7 @@ public static TheoryData Issuer_ExtensibilityTest "IssuerValidatorDelegate", issuerGuid, CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync, - [ - new StackFrame("CustomIssuerValidationDelegates", 169), - new StackFrame(false), - new StackFrame(false) - ]) + extraStackFrames: 2) { ExpectedException = new ExpectedException( typeof(SecurityTokenInvalidIssuerException), @@ -179,7 +162,7 @@ public static TheoryData Issuer_ExtensibilityTest nameof(CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync), null), ValidationFailureType.IssuerValidationFailed, typeof(SecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates", 169), + new StackFrame("CustomIssuerValidationDelegates.cs", 169), issuerGuid) }); @@ -188,21 +171,20 @@ public static TheoryData Issuer_ExtensibilityTest "IssuerValidatorCustomIssuerExceptionTypeDelegate", issuerGuid, CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync, - [ - new StackFrame("CustomIssuerValidationDelegates", 196), - new StackFrame(false), - new StackFrame(false) - ]) + extraStackFrames: 2) { - ExpectedException = new ExpectedException( - typeof(SecurityTokenException), - nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync)), + // IssuerValidationError does not handle the exception type 'CustomSecurityTokenInvalidIssuerException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync))), IssuerValidationError = new IssuerValidationError( new MessageDetail( nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync), null), ValidationFailureType.IssuerValidationFailed, typeof(CustomSecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates", 196), + new StackFrame("CustomIssuerValidationDelegates.cs", 196), issuerGuid) }); @@ -211,21 +193,20 @@ public static TheoryData Issuer_ExtensibilityTest "IssuerValidatorCustomExceptionTypeDelegate", issuerGuid, CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync, - [ - new StackFrame("CustomIssuerValidationDelegates", 210), - new StackFrame(false), - new StackFrame(false) - ]) + extraStackFrames: 2) { - ExpectedException = new ExpectedException( - typeof(SecurityTokenException), - nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync)), + // IssuerValidationError 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(CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync))), IssuerValidationError = new IssuerValidationError( new MessageDetail( nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync), null), ValidationFailureType.IssuerValidationFailed, typeof(CustomSecurityTokenException), - new StackFrame("CustomIssuerValidationDelegates", 210), + new StackFrame("CustomIssuerValidationDelegates.cs", 210), issuerGuid) }); @@ -234,10 +215,7 @@ public static TheoryData Issuer_ExtensibilityTest "IssuerValidatorThrows", issuerGuid, CustomIssuerValidationDelegates.IssuerValidatorThrows, - [ - new StackFrame("JsonWebTokenHandler.ValidateToken.Internal.cs", 300), - new StackFrame(false) - ]) + extraStackFrames: 1) { ExpectedException = new ExpectedException( typeof(SecurityTokenInvalidIssuerException), @@ -261,7 +239,7 @@ public static TheoryData Issuer_ExtensibilityTest public class IssuerExtensibilityTheoryData : ValidateTokenAsyncBaseTheoryData { - internal IssuerExtensibilityTheoryData(string testId, string issuer, IssuerValidationDelegateAsync issuerValidator, IList stackFrames) : base(testId) + internal IssuerExtensibilityTheoryData(string testId, string issuer, IssuerValidationDelegateAsync issuerValidator, int extraStackFrames) : base(testId) { JsonWebToken = JsonUtilities.CreateUnsignedJsonWebToken("iss", issuer); ValidationParameters = new ValidationParameters @@ -276,7 +254,7 @@ internal IssuerExtensibilityTheoryData(string testId, string issuer, IssuerValid TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation }; - StackFrames = stackFrames; + ExtraStackFrames = extraStackFrames; } public JsonWebToken JsonWebToken { get; } @@ -289,7 +267,7 @@ internal IssuerExtensibilityTheoryData(string testId, string issuer, IssuerValid internal IssuerValidationError? IssuerValidationError { get; set; } - internal IList StackFrames { get; } + internal int ExtraStackFrames { get; } } } } diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Issuer.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Issuer.cs new file mode 100644 index 0000000000..4f5afae851 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Issuer.cs @@ -0,0 +1,278 @@ +// 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.Logging; +using Microsoft.IdentityModel.TestUtils; + +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml2.Extensibility.Tests +{ + public partial class Saml2SecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(Issuer_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_IssuerValidator_Extensibility(IssuerExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_IssuerValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.IssuerValidationError!.AddStackFrame(new StackFrame(false)); + + try + { + ValidationResult validationResult = await theoryData.SamlSecurityTokenHandler.ValidateTokenAsync( + theoryData.SamlToken!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + ValidatedToken validatedToken = validationResult.UnwrapResult(); + if (validatedToken.ValidatedIssuer.HasValue) + IdentityComparer.AreValidatedIssuersEqual(validatedToken.ValidatedIssuer.Value, theoryData.ValidatedIssuer, context); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.IssuerValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData Issuer_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + string issuerGuid = Guid.NewGuid().ToString(); + + #region return CustomIssuerValidationError + // Test cases where delegate is overridden and return an CustomIssuerValidationError + // CustomIssuerValidationError : IssuerValidationError, ExceptionType: SecurityTokenInvalidIssuerException + theoryData.Add(new IssuerExtensibilityTheoryData( + "CustomIssuerValidatorDelegate", + issuerGuid, + CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync)), + IssuerValidationError = new CustomIssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(SecurityTokenInvalidIssuerException), + new StackFrame("CustomIssuerValidationDelegates.cs", 88), + issuerGuid) + }); + + // CustomIssuerValidationError : IssuerValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerException : SecurityTokenInvalidIssuerException + theoryData.Add(new IssuerExtensibilityTheoryData( + "CustomIssuerValidatorCustomExceptionDelegate", + issuerGuid, + CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync)), + IssuerValidationError = new CustomIssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(CustomSecurityTokenInvalidIssuerException), + new StackFrame("CustomIssuerValidationDelegates.cs", 107), + issuerGuid), + }); + + // CustomIssuerValidationError : IssuerValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new IssuerExtensibilityTheoryData( + "CustomIssuerValidatorUnknownExceptionDelegate", + issuerGuid, + CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync, + extraStackFrames: 1) + { + // CustomIssuerValidationError 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(CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync))), + IssuerValidationError = new CustomIssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomIssuerValidationDelegates.cs", 139), + issuerGuid), + }); + + // CustomIssuerValidationError : IssuerValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomIssuerValidationFailureType + theoryData.Add(new IssuerExtensibilityTheoryData( + "CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegate", + issuerGuid, + CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync)), + IssuerValidationError = new CustomIssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync), null), + CustomIssuerValidationError.CustomIssuerValidationFailureType, + typeof(CustomSecurityTokenInvalidIssuerException), + new StackFrame("CustomIssuerValidationDelegates.cs", 123), + issuerGuid), + }); + #endregion + + #region return IssuerValidationError + // Test cases where delegate is overridden and return an IssuerValidationError + // IssuerValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerException + theoryData.Add(new IssuerExtensibilityTheoryData( + "IssuerValidatorDelegate", + issuerGuid, + CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync)), + IssuerValidationError = new IssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(SecurityTokenInvalidIssuerException), + new StackFrame("CustomIssuerValidationDelegates.cs", 169), + issuerGuid) + }); + + // IssuerValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerException : SecurityTokenInvalidIssuerException + theoryData.Add(new IssuerExtensibilityTheoryData( + "IssuerValidatorCustomIssuerExceptionTypeDelegate", + issuerGuid, + CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync, + extraStackFrames: 1) + { + // IssuerValidationError does not handle the exception type 'CustomSecurityTokenInvalidIssuerException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync))), + IssuerValidationError = new IssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(CustomSecurityTokenInvalidIssuerException), + new StackFrame("CustomIssuerValidationDelegates.cs", 196), + issuerGuid) + }); + + // IssuerValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new IssuerExtensibilityTheoryData( + "IssuerValidatorCustomExceptionTypeDelegate", + issuerGuid, + CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync, + extraStackFrames: 1) + { + // IssuerValidationError 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(CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync))), + IssuerValidationError = new IssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomIssuerValidationDelegates.cs", 210), + issuerGuid) + }); + + // IssuerValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerException, inner: CustomSecurityTokenInvalidIssuerException + theoryData.Add(new IssuerExtensibilityTheoryData( + "IssuerValidatorThrows", + issuerGuid, + CustomIssuerValidationDelegates.IssuerValidatorThrows, + extraStackFrames: 0) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidIssuerException), + string.Format(Tokens.LogMessages.IDX10269), + typeof(CustomSecurityTokenInvalidIssuerException)), + IssuerValidationError = new IssuerValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10269), null), + ValidationFailureType.IssuerValidatorThrew, + typeof(SecurityTokenInvalidIssuerException), + new StackFrame("Saml2SecurityTokenHandler.ValidateToken.Internal.cs", 95), + issuerGuid, + new SecurityTokenInvalidIssuerException(nameof(CustomIssuerValidationDelegates.IssuerValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } + + public class IssuerExtensibilityTheoryData : TheoryDataBase + { + internal IssuerExtensibilityTheoryData(string testId, string issuer, IssuerValidationDelegateAsync issuerValidator, int extraStackFrames) : base(testId) + { + SamlToken = (Saml2SecurityToken)SamlSecurityTokenHandler.CreateToken(new() + { + Subject = Default.SamlClaimsIdentity, + Issuer = issuer, + }); + + ValidationParameters = new ValidationParameters + { + AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = issuerValidator, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + SignatureValidator = SkipValidationDelegates.SkipSignatureValidation, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation + }; + + ExtraStackFrames = extraStackFrames; + } + + public Saml2SecurityToken SamlToken { get; } + + public Saml2SecurityTokenHandler SamlSecurityTokenHandler { get; } = new Saml2SecurityTokenHandler(); + + public bool IsValid { get; set; } + + internal ValidatedIssuer ValidatedIssuer { get; set; } + + internal ValidationParameters? ValidationParameters { get; set; } + + internal IssuerValidationError? IssuerValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Issuer.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Issuer.cs new file mode 100644 index 0000000000..af2e6822d9 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Issuer.cs @@ -0,0 +1,278 @@ +// 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.Logging; +using Microsoft.IdentityModel.TestUtils; + +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml.Extensibility.Tests +{ + public partial class SamlSecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(Issuer_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_IssuerValidator_Extensibility(IssuerExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_IssuerValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.IssuerValidationError!.AddStackFrame(new StackFrame(false)); + + try + { + ValidationResult validationResult = await theoryData.SamlSecurityTokenHandler.ValidateTokenAsync( + theoryData.SamlToken!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + ValidatedToken validatedToken = validationResult.UnwrapResult(); + if (validatedToken.ValidatedIssuer.HasValue) + IdentityComparer.AreValidatedIssuersEqual(validatedToken.ValidatedIssuer.Value, theoryData.ValidatedIssuer, context); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.IssuerValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData Issuer_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + string issuerGuid = Guid.NewGuid().ToString(); + + #region return CustomIssuerValidationError + // Test cases where delegate is overridden and return an CustomIssuerValidationError + // CustomIssuerValidationError : IssuerValidationError, ExceptionType: SecurityTokenInvalidIssuerException + theoryData.Add(new IssuerExtensibilityTheoryData( + "CustomIssuerValidatorDelegate", + issuerGuid, + CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync)), + IssuerValidationError = new CustomIssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(SecurityTokenInvalidIssuerException), + new StackFrame("CustomIssuerValidationDelegates.cs", 88), + issuerGuid) + }); + + // CustomIssuerValidationError : IssuerValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerException : SecurityTokenInvalidIssuerException + theoryData.Add(new IssuerExtensibilityTheoryData( + "CustomIssuerValidatorCustomExceptionDelegate", + issuerGuid, + CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync)), + IssuerValidationError = new CustomIssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(CustomSecurityTokenInvalidIssuerException), + new StackFrame("CustomIssuerValidationDelegates.cs", 107), + issuerGuid), + }); + + // CustomIssuerValidationError : IssuerValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new IssuerExtensibilityTheoryData( + "CustomIssuerValidatorUnknownExceptionDelegate", + issuerGuid, + CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync, + extraStackFrames: 1) + { + // CustomIssuerValidationError 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(CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync))), + IssuerValidationError = new CustomIssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomIssuerValidationDelegates.cs", 139), + issuerGuid), + }); + + // CustomIssuerValidationError : IssuerValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomIssuerValidationFailureType + theoryData.Add(new IssuerExtensibilityTheoryData( + "CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegate", + issuerGuid, + CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync)), + IssuerValidationError = new CustomIssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync), null), + CustomIssuerValidationError.CustomIssuerValidationFailureType, + typeof(CustomSecurityTokenInvalidIssuerException), + new StackFrame("CustomIssuerValidationDelegates.cs", 123), + issuerGuid), + }); + #endregion + + #region return IssuerValidationError + // Test cases where delegate is overridden and return an IssuerValidationError + // IssuerValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerException + theoryData.Add(new IssuerExtensibilityTheoryData( + "IssuerValidatorDelegate", + issuerGuid, + CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync)), + IssuerValidationError = new IssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(SecurityTokenInvalidIssuerException), + new StackFrame("CustomIssuerValidationDelegates.cs", 169), + issuerGuid) + }); + + // IssuerValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerException : SecurityTokenInvalidIssuerException + theoryData.Add(new IssuerExtensibilityTheoryData( + "IssuerValidatorCustomIssuerExceptionTypeDelegate", + issuerGuid, + CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync, + extraStackFrames: 1) + { + // IssuerValidationError does not handle the exception type 'CustomSecurityTokenInvalidIssuerException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync))), + IssuerValidationError = new IssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(CustomSecurityTokenInvalidIssuerException), + new StackFrame("CustomIssuerValidationDelegates.cs", 196), + issuerGuid) + }); + + // IssuerValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new IssuerExtensibilityTheoryData( + "IssuerValidatorCustomExceptionTypeDelegate", + issuerGuid, + CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync, + extraStackFrames: 1) + { + // IssuerValidationError 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(CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync))), + IssuerValidationError = new IssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomIssuerValidationDelegates.cs", 210), + issuerGuid) + }); + + // IssuerValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerException, inner: CustomSecurityTokenInvalidIssuerException + theoryData.Add(new IssuerExtensibilityTheoryData( + "IssuerValidatorThrows", + issuerGuid, + CustomIssuerValidationDelegates.IssuerValidatorThrows, + extraStackFrames: 0) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidIssuerException), + string.Format(Tokens.LogMessages.IDX10269), + typeof(CustomSecurityTokenInvalidIssuerException)), + IssuerValidationError = new IssuerValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10269), null), + ValidationFailureType.IssuerValidatorThrew, + typeof(SecurityTokenInvalidIssuerException), + new StackFrame("SamlSecurityTokenHandler.ValidateToken.Internal.cs", 92), + issuerGuid, + new SecurityTokenInvalidIssuerException(nameof(CustomIssuerValidationDelegates.IssuerValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } + + public class IssuerExtensibilityTheoryData : TheoryDataBase + { + internal IssuerExtensibilityTheoryData(string testId, string issuer, IssuerValidationDelegateAsync issuerValidator, int extraStackFrames) : base(testId) + { + SamlToken = (SamlSecurityToken)SamlSecurityTokenHandler.CreateToken(new() + { + Subject = Default.SamlClaimsIdentity, + Issuer = issuer, + }); + + ValidationParameters = new ValidationParameters + { + AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = issuerValidator, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + SignatureValidator = SkipValidationDelegates.SkipSignatureValidation, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation + }; + + ExtraStackFrames = extraStackFrames; + } + + public SamlSecurityToken SamlToken { get; } + + public SamlSecurityTokenHandler SamlSecurityTokenHandler { get; } = new SamlSecurityTokenHandler(); + + public bool IsValid { get; set; } + + internal ValidatedIssuer ValidatedIssuer { get; set; } + + internal ValidationParameters? ValidationParameters { get; set; } + + internal IssuerValidationError? IssuerValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } + } +} +#nullable restore From 27bc920f1687fa76eb4ceac4790233811ee6f2e8 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Mon, 25 Nov 2024 18:27:19 +0000 Subject: [PATCH 04/23] Ignore the inner exception type in the test (#3037) --- .../PopKeyResolvingTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/PopKeyResolvingTests.cs b/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/PopKeyResolvingTests.cs index ef06cc18c5..70cf3eb5ce 100644 --- a/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/PopKeyResolvingTests.cs +++ b/test/Microsoft.IdentityModel.Protocols.SignedHttpRequest.Tests/PopKeyResolvingTests.cs @@ -813,7 +813,7 @@ public static TheoryData GetPopKeysFromJkuAsyncTheoryDa { RequireHttpsForJkuResourceRetrieval = false, }, - ExpectedException = new ExpectedException(typeof(SignedHttpRequestInvalidPopKeyException), "IDX23022", typeof(ArgumentException)), + ExpectedException = new ExpectedException(typeof(SignedHttpRequestInvalidPopKeyException), "IDX23022", null, true), }, new ResolvePopKeyTheoryData("Valid0KeysReturned") { From f8b5bb6944d82390ca3440e0a8a2514bea3adc52 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Mon, 25 Nov 2024 22:12:26 +0000 Subject: [PATCH 05/23] Extensibility tests: Algorithm and Signature - JWT, SAML and SAML2 (#3034) * Added SecurityTokenInvalidOperationException to replace InvalidOperationException when used with ValidationError subclasses (cherry picked from commit 0c59aef754ea2735be96769f9560a900a58d324b) * Added SignatureValidationError to handle exceptions generated during signature validation * Removed XmlValidationError. Updated XML signature validation to use SignatureValidationError instead * Added log messages, validation failure types. Removed nullability of CallContext in the signature validation delegate. * Handle potential exceptions thrown during signature validation, both signature and algorithm related * Updated validation errors and test * Added custom validation errors and delegates for algorithm and signature validation * Added algorithm extensibility tests for JWT, SAML, and SAML2 (cherry picked from commit 462e576d83d01c3e2e8766891b6033cc7be99e4c) * Added signature extensibility tests for JWT, SAML, and SAML2 (cherry picked from commit 1ec57411a1cf40b79d99414d0e6afce2b8cdf101) --- .../JsonWebTokenHandler.ValidateSignature.cs | 144 ++++---- ...lSecurityTokenHandler.ValidateSignature.cs | 121 ++++--- ...2SecurityTokenHandler.ValidateSignature.cs | 123 ++++--- .../Delegates.cs | 6 +- .../SecurityTokenInvalidOperationException.cs | 41 +++ .../InternalAPI.Unshipped.txt | 25 +- .../LogMessages.cs | 2 + .../Details/AlgorithmValidationError.cs | 9 +- .../Details/SignatureValidationError.cs | 57 +++ .../Results/Details/ValidationError.cs | 4 + .../Validation/ValidationFailureType.cs | 10 + .../Exceptions/XmlValidationError.cs | 37 -- .../InternalAPI.Unshipped.txt | 6 +- src/Microsoft.IdentityModel.Xml/Reference.cs | 12 +- src/Microsoft.IdentityModel.Xml/Signature.cs | 30 +- src/Microsoft.IdentityModel.Xml/SignedInfo.cs | 8 +- ...WebTokenHandler.Extensibility.Algorithm.cs | 334 +++++++++++++++++ ...WebTokenHandler.Extensibility.Signature.cs | 266 ++++++++++++++ ...ndler.ValidateTokenAsyncTests.Algorithm.cs | 4 +- .../SkipValidationDelegates.cs | 2 +- .../CustomAlgorithmValidationDelegates.cs | 144 ++++++++ .../CustomSignatureValidationDelegates.cs | 127 +++++++ .../CustomValidationErrors.cs | 92 +++++ ...ityTokenHandler.Extensibility.Algorithm.cs | 337 ++++++++++++++++++ ...ityTokenHandler.Extensibility.Signature.cs | 270 ++++++++++++++ ...alidateTokenAsyncTests.IssuerSigningKey.cs | 2 +- ...ityTokenHandler.Extensibility.Algorithm.cs | 337 ++++++++++++++++++ ...ityTokenHandler.Extensibility.Signature.cs | 270 ++++++++++++++ ...alidateTokenAsyncTests.IssuerSigningKey.cs | 2 +- 29 files changed, 2601 insertions(+), 221 deletions(-) create mode 100644 src/Microsoft.IdentityModel.Tokens/Exceptions/SecurityTokenInvalidOperationException.cs create mode 100644 src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/SignatureValidationError.cs delete mode 100644 src/Microsoft.IdentityModel.Xml/Exceptions/XmlValidationError.cs create mode 100644 test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Algorithm.cs create mode 100644 test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Signature.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomAlgorithmValidationDelegates.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomSignatureValidationDelegates.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Algorithm.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Signature.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Algorithm.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Signature.cs diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateSignature.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateSignature.cs index ff0a9637a3..1c9e490f7a 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateSignature.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateSignature.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Text; using Microsoft.IdentityModel.Logging; @@ -23,7 +22,7 @@ public partial class JsonWebTokenHandler : TokenHandler /// The parameters used for validation. /// The optional configuration used for validation. /// The context in which the method is called. - /// Returned if or is null." + /// Returned if or is null." /// Returned by the default implementation if the token is not signed, or if the validation fails. /// Returned if the algorithm is not supported by the key. /// Returned if the key cannot be resolved. @@ -34,22 +33,47 @@ internal static ValidationResult ValidateSignature( CallContext callContext) { if (jwtToken is null) - return ValidationError.NullParameter( + return SignatureValidationError.NullParameter( nameof(jwtToken), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); if (validationParameters is null) - return ValidationError.NullParameter( + return SignatureValidationError.NullParameter( nameof(validationParameters), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); // Delegate is set by the user, we call it and return the result. if (validationParameters.SignatureValidator is not null) - return validationParameters.SignatureValidator(jwtToken, validationParameters, configuration, callContext); + { + try + { + ValidationResult signatureValidationResult = validationParameters.SignatureValidator( + jwtToken, + validationParameters, + configuration, + callContext); + + if (!signatureValidationResult.IsValid) + return signatureValidationResult.UnwrapError().AddCurrentStackFrame(); + + return signatureValidationResult; + } +#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 SignatureValidationError( + new MessageDetail(TokenLogMessages.IDX10272), + ValidationFailureType.SignatureValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + innerException: ex); + } + } // If the user wants to accept unsigned tokens, they must implement the delegate. if (!jwtToken.IsSigned) - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10504, LogHelper.MarkAsSecurityArtifact( @@ -57,7 +81,7 @@ internal static ValidationResult ValidateSignature( JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenInvalidSignatureException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); SecurityKey? key = null; if (validationParameters.IssuerSigningKeyResolver is not null) @@ -93,8 +117,7 @@ internal static ValidationResult ValidateSignature( { if (!string.IsNullOrEmpty(jwtToken.Kid)) { - StackFrame kidNotMatchedNoTryAllStackFrame = StackFrames.KidNotMatchedNoTryAll ??= new StackFrame(true); - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10502, LogHelper.MarkAsNonPII(jwtToken.Kid), @@ -103,15 +126,14 @@ internal static ValidationResult ValidateSignature( LogHelper.MarkAsSecurityArtifact(jwtToken.EncodedToken, JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), - kidNotMatchedNoTryAllStackFrame); + ValidationError.GetCurrentStackFrame()); } - StackFrame noKeysProvidedStackFrame = StackFrames.NoKeysProvided ??= new StackFrame(true); - return new ValidationError( + return new SignatureValidationError( new MessageDetail(TokenLogMessages.IDX10500), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), - noKeysProvidedStackFrame); + ValidationError.GetCurrentStackFrame()); } } @@ -144,11 +166,11 @@ private static ValidationResult ValidateSignatureUsingAllKeys( return unwrappedVpResult; if (vpFailedResult is null && configFailedResult is null) // No keys were attempted - return new ValidationError( + return new SignatureValidationError( new MessageDetail(TokenLogMessages.IDX10500), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); StringBuilder exceptionStrings = new(); StringBuilder keysAttempted = new(); @@ -223,61 +245,63 @@ private static ValidationResult ValidateSignatureWithKey( CryptoProviderFactory cryptoProviderFactory = validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory; if (!cryptoProviderFactory.IsSupportedAlgorithm(jsonWebToken.Alg, key)) { - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10400, LogHelper.MarkAsNonPII(jsonWebToken.Alg), key), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenInvalidAlgorithmException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); } - ValidationResult result = validationParameters.AlgorithmValidator( - jsonWebToken.Alg, - key, - jsonWebToken, - validationParameters, - callContext); - - if (!result.IsValid) + try { - if (result.UnwrapError() is AlgorithmValidationError algorithmValidationError) - { - return new AlgorithmValidationError( - new MessageDetail( - TokenLogMessages.IDX10518, - algorithmValidationError.MessageDetail.Message), - ValidationFailureType.AlgorithmValidationFailed, - typeof(SecurityTokenInvalidAlgorithmException), - new StackFrame(true), - algorithmValidationError.InvalidAlgorithm); - } - else + ValidationResult algorithmValidationResult = validationParameters.AlgorithmValidator( + jsonWebToken.Alg, + key, + jsonWebToken, + validationParameters, + callContext); + + if (!algorithmValidationResult.IsValid) { - // overridden delegate did not return an AlgorithmValidationError - return new ValidationError( + var validationError = algorithmValidationResult.UnwrapError().AddCurrentStackFrame(); + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10518, - result.UnwrapError().MessageDetail.Message), - ValidationFailureType.SignatureAlgorithmValidationFailed, - typeof(SecurityTokenInvalidAlgorithmException), - new StackFrame(true)); + validationError.MessageDetail.Message), + validationError.FailureType, // Surface the algorithm validation error's failure type. + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + validationError); // Pass the algorithm validation error as the inner validation error. } } +#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 SignatureValidationError( + new MessageDetail(TokenLogMessages.IDX10273), + ValidationFailureType.AlgorithmValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + null, // No need to create an AlgorithmValidationError for this case. + ex); + } SignatureProvider signatureProvider = cryptoProviderFactory.CreateForVerifying(key, jsonWebToken.Alg); try { if (signatureProvider == null) - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10636, key?.ToString() ?? "Null", LogHelper.MarkAsNonPII(jsonWebToken.Alg)), ValidationFailureType.SignatureValidationFailed, - typeof(InvalidOperationException), - new StackFrame(true)); + typeof(SecurityTokenInvalidOperationException), + ValidationError.GetCurrentStackFrame()); bool valid = EncodingUtils.PerformEncodingDependentOperation( jsonWebToken.EncodedToken, @@ -292,7 +316,7 @@ private static ValidationResult ValidateSignatureWithKey( if (valid) return key; else - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10504, LogHelper.MarkAsSecurityArtifact( @@ -300,13 +324,13 @@ private static ValidationResult ValidateSignatureWithKey( JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenInvalidSignatureException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); } #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 ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10504, LogHelper.MarkAsSecurityArtifact( @@ -314,8 +338,8 @@ private static ValidationResult ValidateSignatureWithKey( JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenInvalidSignatureException), - new StackFrame(true), - ex); + ValidationError.GetCurrentStackFrame(), + innerException: ex); } finally { @@ -323,7 +347,7 @@ private static ValidationResult ValidateSignatureWithKey( } } - private static ValidationError GetSignatureValidationError( + private static SignatureValidationError GetSignatureValidationError( JsonWebToken jwtToken, ValidationParameters validationParameters, BaseConfiguration? configuration, @@ -343,7 +367,7 @@ private static ValidationError GetSignatureValidationError( JsonWebToken localJwtToken = jwtToken; // avoid closure on non-exceptional path bool isKidInTVP = keysInTokenValidationParameters.Any(x => x.KeyId.Equals(localJwtToken.Kid)); string keyLocation = isKidInTVP ? "TokenValidationParameters" : "Configuration"; - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10511, LogHelper.MarkAsNonPII(keysAttempted.ToString()), @@ -355,11 +379,11 @@ private static ValidationError GetSignatureValidationError( LogHelper.MarkAsSecurityArtifact(jwtToken.EncodedToken, JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); } if (kidExists) - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10503, LogHelper.MarkAsNonPII(jwtToken.Kid), @@ -370,9 +394,9 @@ private static ValidationError GetSignatureValidationError( LogHelper.MarkAsSecurityArtifact(jwtToken.EncodedToken, JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); - return new ValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10517, // Kid is missing and no keys match. LogHelper.MarkAsNonPII(keysAttempted.ToString()), @@ -382,7 +406,7 @@ private static ValidationError GetSignatureValidationError( LogHelper.MarkAsSecurityArtifact(jwtToken.EncodedToken, JwtTokenUtilities.SafeLogJwtToken)), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); } private static void PopulateFailedResults( diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateSignature.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateSignature.cs index 3691a62159..5681070c45 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateSignature.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateSignature.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; using System.Text; -using Microsoft.IdentityModel.Xml; using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages; #nullable enable @@ -20,25 +20,50 @@ internal static ValidationResult ValidateSignature( { if (samlToken is null) { - return ValidationError.NullParameter( + return SignatureValidationError.NullParameter( nameof(samlToken), ValidationError.GetCurrentStackFrame()); } if (validationParameters is null) { - return ValidationError.NullParameter( + return SignatureValidationError.NullParameter( nameof(validationParameters), ValidationError.GetCurrentStackFrame()); } // Delegate is set by the user, we call it and return the result. if (validationParameters.SignatureValidator is not null) - return validationParameters.SignatureValidator(samlToken, validationParameters, null, callContext); + { + try + { + ValidationResult signatureValidationResult = validationParameters.SignatureValidator( + samlToken, + validationParameters, + null, // configuration + callContext); + + if (!signatureValidationResult.IsValid) + return signatureValidationResult.UnwrapError().AddCurrentStackFrame(); + + return signatureValidationResult; + } +#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 SignatureValidationError( + new MessageDetail(TokenLogMessages.IDX10272), + ValidationFailureType.SignatureValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + innerException: ex); + } + } // If the user wants to accept unsigned tokens, they must implement the delegate if (samlToken.Assertion.Signature is null) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10504, samlToken.Assertion.CanonicalString), @@ -64,16 +89,15 @@ internal static ValidationResult ValidateSignature( resolvedKey = SamlTokenUtilities.ResolveTokenSigningKey(samlToken.Assertion.Signature.KeyInfo, validationParameters); } - ValidationError? error = null; - if (resolvedKey is not null) { keyMatched = true; var result = ValidateSignatureUsingKey(resolvedKey, samlToken, validationParameters, callContext); - if (result.IsValid) - return result; - error = result.UnwrapError(); + if (!result.IsValid) + return result.UnwrapError().AddCurrentStackFrame(); + + return result; } bool canMatchKey = samlToken.Assertion.Signature.KeyInfo != null; @@ -103,12 +127,12 @@ internal static ValidationResult ValidateSignature( } if (canMatchKey && keyMatched) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10514, keysAttempted?.ToString(), samlToken.Assertion.Signature.KeyInfo, - GetErrorString(error, errors), + GetErrorString(errors), samlToken), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenInvalidSignatureException), @@ -121,17 +145,17 @@ internal static ValidationResult ValidateSignature( keysAttemptedString = keysAttempted!.ToString(); if (keysAttemptedString is not null) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10512, keysAttemptedString, - GetErrorString(error, errors), + GetErrorString(errors), samlToken), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), ValidationError.GetCurrentStackFrame()); - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail(TokenLogMessages.IDX10500), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), @@ -140,44 +164,61 @@ internal static ValidationResult ValidateSignature( private static ValidationResult ValidateSignatureUsingKey(SecurityKey key, SamlSecurityToken samlToken, ValidationParameters validationParameters, CallContext callContext) { - ValidationResult algorithmValidationResult = validationParameters.AlgorithmValidator( - samlToken.Assertion.Signature.SignedInfo.SignatureMethod, - key, - samlToken, - validationParameters, - callContext); - - if (!algorithmValidationResult.IsValid) + try { - return algorithmValidationResult.UnwrapError().AddCurrentStackFrame(); + ValidationResult algorithmValidationResult = validationParameters.AlgorithmValidator( + samlToken.Assertion.Signature.SignedInfo.SignatureMethod, + key, + samlToken, + validationParameters, + callContext); + + if (!algorithmValidationResult.IsValid) + { + var algorithmValidationError = algorithmValidationResult.UnwrapError().AddCurrentStackFrame(); + return new SignatureValidationError( + new MessageDetail( + TokenLogMessages.IDX10518, + algorithmValidationError.MessageDetail.Message), + algorithmValidationError.FailureType, // Surface the algorithm validation error's failure type. + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + algorithmValidationError); // Pass the algorithm validation error as the inner validation error. + } } - else +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { - var validationError = samlToken.Assertion.Signature.Verify( + return new SignatureValidationError( + new MessageDetail(TokenLogMessages.IDX10273), + ValidationFailureType.AlgorithmValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + null, // No need to create an AlgorithmValidationError for this case. + ex); + } + + var validationError = samlToken.Assertion.Signature.Verify( key, validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory, callContext); - if (validationError is null) - { - samlToken.SigningKey = key; + if (validationError is null) + { + samlToken.SigningKey = key; - return key; - } - else - { - return validationError.AddCurrentStackFrame(); - } + return key; + } + else + { + return validationError.AddCurrentStackFrame(); } } - private static string GetErrorString(ValidationError? error, List? errorList) + private static string GetErrorString(List? errorList) { // This method is called if there are errors in the signature validation process. - // This check is there to account for the optional parameter. - if (error is not null) - return error.MessageDetail.Message; - if (errorList is null) return string.Empty; diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateSignature.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateSignature.cs index 74d2884b26..7aeb16093e 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateSignature.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateSignature.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Collections.Generic; using System.Text; using Microsoft.IdentityModel.Tokens.Saml; -using Microsoft.IdentityModel.Xml; using TokenLogMessages = Microsoft.IdentityModel.Tokens.LogMessages; #nullable enable @@ -19,25 +19,50 @@ internal static ValidationResult ValidateSignature( { if (samlToken is null) { - return ValidationError.NullParameter( + return SignatureValidationError.NullParameter( nameof(samlToken), ValidationError.GetCurrentStackFrame()); } if (validationParameters is null) { - return ValidationError.NullParameter( + return SignatureValidationError.NullParameter( nameof(validationParameters), ValidationError.GetCurrentStackFrame()); } // Delegate is set by the user, we call it and return the result. if (validationParameters.SignatureValidator is not null) - return validationParameters.SignatureValidator(samlToken, validationParameters, null, callContext); + { + try + { + ValidationResult signatureValidationResult = validationParameters.SignatureValidator( + samlToken, + validationParameters, + null, // configuration + callContext); + + if (!signatureValidationResult.IsValid) + return signatureValidationResult.UnwrapError().AddCurrentStackFrame(); + + return signatureValidationResult; + } +#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 SignatureValidationError( + new MessageDetail(TokenLogMessages.IDX10272), + ValidationFailureType.SignatureValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + innerException: ex); + } + } // If the user wants to accept unsigned tokens, they must set validationParameters.SignatureValidator if (samlToken.Assertion.Signature is null) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10504, samlToken.Assertion.CanonicalString), @@ -63,16 +88,14 @@ internal static ValidationResult ValidateSignature( resolvedKey = SamlTokenUtilities.ResolveTokenSigningKey(samlToken.Assertion.Signature.KeyInfo, validationParameters); } - ValidationError? error = null; - if (resolvedKey is not null) { keyMatched = true; var result = ValidateSignatureUsingKey(resolvedKey, samlToken, validationParameters, callContext); - if (result.IsValid) - return result; + if (!result.IsValid) + return result.UnwrapError().AddCurrentStackFrame(); - error = result.UnwrapError(); + return result; } bool canMatchKey = samlToken.Assertion.Signature.KeyInfo != null; @@ -102,12 +125,12 @@ internal static ValidationResult ValidateSignature( } if (canMatchKey && keyMatched) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10514, keysAttempted?.ToString(), samlToken.Assertion.Signature.KeyInfo, - GetErrorStrings(error, errors), + GetErrorStrings(errors), samlToken), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenInvalidSignatureException), @@ -120,17 +143,17 @@ internal static ValidationResult ValidateSignature( keysAttemptedString = keysAttempted!.ToString(); if (keysAttemptedString is not null) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail( TokenLogMessages.IDX10512, keysAttemptedString, - GetErrorStrings(error, errors), + GetErrorStrings(errors), samlToken), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), ValidationError.GetCurrentStackFrame()); - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail(TokenLogMessages.IDX10500), ValidationFailureType.SignatureValidationFailed, typeof(SecurityTokenSignatureKeyNotFoundException), @@ -139,43 +162,61 @@ internal static ValidationResult ValidateSignature( private static ValidationResult ValidateSignatureUsingKey(SecurityKey key, Saml2SecurityToken samlToken, ValidationParameters validationParameters, CallContext callContext) { - ValidationResult algorithmValidationResult = validationParameters.AlgorithmValidator( - samlToken.Assertion.Signature.SignedInfo.SignatureMethod, - key, - samlToken, - validationParameters, - callContext); - - if (!algorithmValidationResult.IsValid) + try { - return algorithmValidationResult.UnwrapError().AddCurrentStackFrame(); - } - else - { - var validationError = samlToken.Assertion.Signature.Verify( + ValidationResult algorithmValidationResult = validationParameters.AlgorithmValidator( + samlToken.Assertion.Signature.SignedInfo.SignatureMethod, key, - validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory, + samlToken, + validationParameters, callContext); - if (validationError is null) + if (!algorithmValidationResult.IsValid) { - samlToken.SigningKey = key; - - return key; - } - else - { - return validationError.AddCurrentStackFrame(); + var algorithmValidationError = algorithmValidationResult.UnwrapError().AddCurrentStackFrame(); + return new SignatureValidationError( + new MessageDetail( + TokenLogMessages.IDX10518, + algorithmValidationError.MessageDetail.Message), + algorithmValidationError.FailureType, // Surface the algorithm validation error's failure type. + typeof(SecurityTokenInvalidSignatureException), + SignatureValidationError.GetCurrentStackFrame(), + algorithmValidationError); // Pass the algorithm validation error as the inner validation error. } } +#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 SignatureValidationError( + new MessageDetail(TokenLogMessages.IDX10273), + ValidationFailureType.AlgorithmValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame(), + null, // No need to create an AlgorithmValidationError for this case. + ex); + } + + var validationError = samlToken.Assertion.Signature.Verify( + key, + validationParameters.CryptoProviderFactory ?? key.CryptoProviderFactory, + callContext); + + if (validationError is null) + { + samlToken.SigningKey = key; + + return key; + } + else + { + return validationError.AddCurrentStackFrame(); + } } - private static string GetErrorStrings(ValidationError? error, List? errors) + private static string GetErrorStrings(List? errors) { // This method is called if there are errors in the signature validation process. - // This check is there to account for the optional parameter. - if (error is not null) - return error.MessageDetail.Message; if (errors is null) return string.Empty; diff --git a/src/Microsoft.IdentityModel.Tokens/Delegates.cs b/src/Microsoft.IdentityModel.Tokens/Delegates.cs index e116fc4662..876d267345 100644 --- a/src/Microsoft.IdentityModel.Tokens/Delegates.cs +++ b/src/Microsoft.IdentityModel.Tokens/Delegates.cs @@ -204,6 +204,10 @@ namespace Microsoft.IdentityModel.Tokens /// The to be used for logging. /// This method is not expected to throw. /// The validated . - internal delegate ValidationResult SignatureValidationDelegate(SecurityToken token, ValidationParameters validationParameters, BaseConfiguration? configuration, CallContext? callContext); + internal delegate ValidationResult SignatureValidationDelegate( + SecurityToken token, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext); #nullable restore } diff --git a/src/Microsoft.IdentityModel.Tokens/Exceptions/SecurityTokenInvalidOperationException.cs b/src/Microsoft.IdentityModel.Tokens/Exceptions/SecurityTokenInvalidOperationException.cs new file mode 100644 index 0000000000..61cf94c224 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Exceptions/SecurityTokenInvalidOperationException.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Runtime.Serialization; + +namespace Microsoft.IdentityModel.Tokens +{ + /// + /// Throw this exception when a method call is invalid for the object's current state. + /// + [Serializable] + internal class SecurityTokenInvalidOperationException : InvalidOperationException + { + /// + /// Initializes a new instance of the class. + /// + public SecurityTokenInvalidOperationException() : base() { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The error message that explains the reason for the exception. + public SecurityTokenInvalidOperationException(string message) : base(message) { } + + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The that is the cause of the current exception, or a null reference if no inner exception is specified. + public SecurityTokenInvalidOperationException(string message, Exception innerException) : base(message, innerException) { } + + /// + /// Initializes a new instance of the class. + /// + /// the that holds the serialized object data. + /// The contextual information about the source or destination. + protected SecurityTokenInvalidOperationException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt index 15dcd06514..cbfa1e30c6 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -1,20 +1,18 @@ 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.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 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 Microsoft.IdentityModel.Tokens.AlgorithmValidationError.InvalidAlgorithm.get -> string -Microsoft.IdentityModel.Tokens.AlgorithmValidationError._invalidAlgorithm -> string Microsoft.IdentityModel.Tokens.AudienceValidationError.AudienceValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Collections.Generic.IList tokenAudiences, System.Collections.Generic.IList validAudiences, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.AudienceValidationError.TokenAudiences.get -> System.Collections.Generic.IList Microsoft.IdentityModel.Tokens.AudienceValidationError.TokenAudiences.set -> void Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidAudiences.get -> System.Collections.Generic.IList Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidAudiences.set -> void -Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError -Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.InvalidSigningKey.get -> Microsoft.IdentityModel.Tokens.SecurityKey -Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.InvalidSigningKey.set -> void -Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.IssuerSigningKeyValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, Microsoft.IdentityModel.Tokens.SecurityKey invalidSigningKey, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType = null, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.IssuerValidationError.InvalidIssuer.get -> string Microsoft.IdentityModel.Tokens.IssuerValidationError.IssuerValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidIssuer, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedConfiguration = 1 -> Microsoft.IdentityModel.Tokens.IssuerValidationSource @@ -22,9 +20,17 @@ Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedValidationPar Microsoft.IdentityModel.Tokens.LifetimeValidationError.Expires.get -> System.DateTime? Microsoft.IdentityModel.Tokens.LifetimeValidationError.LifetimeValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.DateTime? notBefore, System.DateTime? expires, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.LifetimeValidationError.NotBefore.get -> System.DateTime? +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTokenInvalidOperationException() -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTokenInvalidOperationException(string message) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTokenInvalidOperationException(string message, System.Exception innerException) -> void +Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTokenInvalidOperationException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) -> void +Microsoft.IdentityModel.Tokens.SignatureValidationError +Microsoft.IdentityModel.Tokens.SignatureValidationError.InnerValidationError.get -> Microsoft.IdentityModel.Tokens.ValidationError +Microsoft.IdentityModel.Tokens.SignatureValidationError.SignatureValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, Microsoft.IdentityModel.Tokens.ValidationError innerValidationError = null, System.Exception innerException = null) -> void 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.get -> string +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.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 @@ -36,16 +42,19 @@ Microsoft.IdentityModel.Tokens.ValidationResult.Error.get -> Microsoft. Microsoft.IdentityModel.Tokens.ValidationResult.IsValid.get -> bool 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.SignatureValidationError.GetException() -> System.Exception override Microsoft.IdentityModel.Tokens.TokenTypeValidationError.GetException() -> System.Exception +static Microsoft.IdentityModel.Tokens.SignatureValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.SignatureValidationError 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.AlgorithmValidatorThrew -> 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 +static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.SignatureValidatorThrew -> 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 diff --git a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs index 8e2cecae39..6a203b74e7 100644 --- a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs +++ b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs @@ -88,6 +88,8 @@ 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 IDX10272 = "IDX10272: SignatureValidationDelegate threw an exception, see inner exception."; + public const string IDX10273 = "IDX10273: AlgorithmValidationDelegate threw an exception, see inner exception."; public const string IDX10275 = "IDX10275: TokenTypeValidationDelegate threw an exception, see inner exception."; // 10500 - SignatureValidation diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AlgorithmValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AlgorithmValidationError.cs index 940d099f32..de0dd174d5 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AlgorithmValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/AlgorithmValidationError.cs @@ -9,8 +9,6 @@ namespace Microsoft.IdentityModel.Tokens { internal class AlgorithmValidationError : ValidationError { - protected string? _invalidAlgorithm; - public AlgorithmValidationError( MessageDetail messageDetail, ValidationFailureType validationFailureType, @@ -20,7 +18,7 @@ public AlgorithmValidationError( Exception? innerException = null) : base(messageDetail, validationFailureType, exceptionType, stackFrame, innerException) { - _invalidAlgorithm = invalidAlgorithm; + InvalidAlgorithm = invalidAlgorithm; } internal override Exception GetException() @@ -29,8 +27,9 @@ internal override Exception GetException() { SecurityTokenInvalidAlgorithmException exception = new(MessageDetail.Message, InnerException) { - InvalidAlgorithm = _invalidAlgorithm + InvalidAlgorithm = InvalidAlgorithm }; + exception.SetValidationError(this); return exception; } @@ -38,7 +37,7 @@ internal override Exception GetException() return base.GetException(); } - internal string? InvalidAlgorithm => _invalidAlgorithm; + protected string? InvalidAlgorithm { get; } } } #nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/SignatureValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/SignatureValidationError.cs new file mode 100644 index 0000000000..78c51069d8 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/SignatureValidationError.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens +{ + internal class SignatureValidationError : ValidationError + { + public SignatureValidationError( + MessageDetail messageDetail, + ValidationFailureType validationFailureType, + Type exceptionType, + StackFrame stackFrame, + ValidationError? innerValidationError = null, + Exception? innerException = null) : + base(messageDetail, validationFailureType, exceptionType, stackFrame, innerException) + { + InnerValidationError = innerValidationError; + } + + internal override Exception GetException() + { + var inner = InnerException ?? InnerValidationError?.GetException(); + + if (ExceptionType == typeof(SecurityTokenInvalidSignatureException)) + { + SecurityTokenInvalidSignatureException exception = new(MessageDetail.Message, inner); + exception.SetValidationError(this); + + return exception; + } + else if (ExceptionType == typeof(SecurityTokenSignatureKeyNotFoundException)) + { + SecurityTokenSignatureKeyNotFoundException exception = new(MessageDetail.Message, inner); + exception.SetValidationError(this); + + return exception; + } + + return base.GetException(); + } + + internal static new SignatureValidationError NullParameter( + string parameterName, StackFrame stackFrame) => new( + MessageDetail.NullParameter(parameterName), + ValidationFailureType.NullArgument, + typeof(SecurityTokenArgumentNullException), + stackFrame, + null); // innerValidationError + + protected internal ValidationError? InnerValidationError { get; } + } +} +#nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs index 8f40ffdadb..5347e72d65 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs @@ -66,6 +66,8 @@ internal Exception GetException(Type exceptionType, Exception? innerException) exception = new SecurityTokenInvalidIssuerException(MessageDetail.Message); else if (exceptionType == typeof(SecurityTokenInvalidLifetimeException)) exception = new SecurityTokenInvalidLifetimeException(MessageDetail.Message); + else if (exceptionType == typeof(SecurityTokenInvalidOperationException)) + exception = new SecurityTokenInvalidOperationException(MessageDetail.Message); else if (exceptionType == typeof(SecurityTokenReplayDetectedException)) exception = new SecurityTokenReplayDetectedException(MessageDetail.Message); else if (exceptionType == typeof(SecurityTokenReplayAddFailedException)) @@ -123,6 +125,8 @@ internal Exception GetException(Type exceptionType, Exception? innerException) exception = new SecurityTokenInvalidIssuerException(MessageDetail.Message, innerException); else if (exceptionType == typeof(SecurityTokenInvalidLifetimeException)) exception = new SecurityTokenInvalidLifetimeException(MessageDetail.Message, innerException); + else if (exceptionType == typeof(SecurityTokenInvalidOperationException)) + exception = new SecurityTokenInvalidOperationException(MessageDetail.Message, innerException); else if (exceptionType == typeof(SecurityTokenReplayDetectedException)) exception = new SecurityTokenReplayDetectedException(MessageDetail.Message, innerException); else if (exceptionType == typeof(SecurityTokenReplayAddFailedException)) diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs index 1f1dcef7af..bcdab9cfbd 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs @@ -129,12 +129,22 @@ private class InvalidSecurityTokenFailure : ValidationFailureType { internal Inv public static readonly ValidationFailureType XmlValidationFailed = new XmlValidationFailure("XmlValidationFailed"); private class XmlValidationFailure : ValidationFailureType { internal XmlValidationFailure(string name) : base(name) { } } + /// + /// Defines a type that represents the fact that the algorithm validation delegate threw an exception. + /// + public static readonly ValidationFailureType AlgorithmValidatorThrew = new AlgorithmValidationFailure("AlgorithmValidatorThrew"); + /// /// Defines a type that represents that a token is invalid. /// 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 signature validation delegate threw an exception. + /// + public static readonly ValidationFailureType SignatureValidatorThrew = new SignatureValidationFailure("SignatureValidatorThrew"); + /// /// Defines a type that represents the fact that the token type validation delegate threw an exception. /// diff --git a/src/Microsoft.IdentityModel.Xml/Exceptions/XmlValidationError.cs b/src/Microsoft.IdentityModel.Xml/Exceptions/XmlValidationError.cs deleted file mode 100644 index a339d58da7..0000000000 --- a/src/Microsoft.IdentityModel.Xml/Exceptions/XmlValidationError.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Diagnostics; -using Microsoft.IdentityModel.Tokens; - -#nullable enable -namespace Microsoft.IdentityModel.Xml -{ - internal class XmlValidationError : ValidationError - { - public XmlValidationError( - MessageDetail messageDetail, - ValidationFailureType validationFailureType, - Type exceptionType, - StackFrame stackFrame, - Exception? innerException = null) : - base(messageDetail, validationFailureType, exceptionType, stackFrame, innerException) - { - - } - - internal override Exception GetException() - { - if (ExceptionType == typeof(XmlValidationException)) - { - XmlValidationException exception = new(MessageDetail.Message, InnerException); - exception.SetValidationError(this); - return exception; - } - - return base.GetException(); - } - } -} -#nullable restore diff --git a/src/Microsoft.IdentityModel.Xml/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Xml/InternalAPI.Unshipped.txt index 59024a50b0..7541dd5624 100644 --- a/src/Microsoft.IdentityModel.Xml/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Xml/InternalAPI.Unshipped.txt @@ -1,6 +1,6 @@ -Microsoft.IdentityModel.Xml.Reference.Verify(Microsoft.IdentityModel.Tokens.CryptoProviderFactory cryptoProviderFactory, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationError -Microsoft.IdentityModel.Xml.Signature.Verify(Microsoft.IdentityModel.Tokens.SecurityKey key, Microsoft.IdentityModel.Tokens.CryptoProviderFactory cryptoProviderFactory, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationError -Microsoft.IdentityModel.Xml.SignedInfo.Verify(Microsoft.IdentityModel.Tokens.CryptoProviderFactory cryptoProviderFactory, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationError +Microsoft.IdentityModel.Xml.Reference.Verify(Microsoft.IdentityModel.Tokens.CryptoProviderFactory cryptoProviderFactory, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.SignatureValidationError +Microsoft.IdentityModel.Xml.Signature.Verify(Microsoft.IdentityModel.Tokens.SecurityKey key, Microsoft.IdentityModel.Tokens.CryptoProviderFactory cryptoProviderFactory, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.SignatureValidationError +Microsoft.IdentityModel.Xml.SignedInfo.Verify(Microsoft.IdentityModel.Tokens.CryptoProviderFactory cryptoProviderFactory, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.SignatureValidationError Microsoft.IdentityModel.Xml.XmlValidationError Microsoft.IdentityModel.Xml.XmlValidationError.XmlValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException = null) -> void Microsoft.IdentityModel.Xml.XmlValidationException.SetValidationError(Microsoft.IdentityModel.Tokens.ValidationError validationError) -> void diff --git a/src/Microsoft.IdentityModel.Xml/Reference.cs b/src/Microsoft.IdentityModel.Xml/Reference.cs index 2a1f6870af..a2534ef1cf 100644 --- a/src/Microsoft.IdentityModel.Xml/Reference.cs +++ b/src/Microsoft.IdentityModel.Xml/Reference.cs @@ -134,23 +134,25 @@ public void Verify(CryptoProviderFactory cryptoProviderFactory) /// supplies the . /// contextual information for diagnostics. /// if is null. - internal ValidationError? Verify( + internal SignatureValidationError? Verify( CryptoProviderFactory cryptoProviderFactory, #pragma warning disable CA1801 // Review unused parameters CallContext callContext) #pragma warning restore CA1801 { if (cryptoProviderFactory == null) - return ValidationError.NullParameter(nameof(cryptoProviderFactory), new System.Diagnostics.StackFrame()); + return SignatureValidationError.NullParameter( + nameof(cryptoProviderFactory), + ValidationError.GetCurrentStackFrame()); if (!Utility.AreEqual(ComputeDigest(cryptoProviderFactory), Convert.FromBase64String(DigestValue))) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail( LogMessages.IDX30201, Uri ?? Id), ValidationFailureType.XmlValidationFailed, - typeof(XmlValidationException), - new System.Diagnostics.StackFrame()); + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame()); return null; } diff --git a/src/Microsoft.IdentityModel.Xml/Signature.cs b/src/Microsoft.IdentityModel.Xml/Signature.cs index b7bf6219fa..5a0d122beb 100644 --- a/src/Microsoft.IdentityModel.Xml/Signature.cs +++ b/src/Microsoft.IdentityModel.Xml/Signature.cs @@ -126,7 +126,7 @@ public void Verify(SecurityKey key, CryptoProviderFactory cryptoProviderFactory) } #nullable enable - internal ValidationError? Verify( + internal SignatureValidationError? Verify( SecurityKey key, CryptoProviderFactory cryptoProviderFactory, #pragma warning disable CA1801 // Review unused parameters @@ -134,34 +134,38 @@ public void Verify(SecurityKey key, CryptoProviderFactory cryptoProviderFactory) #pragma warning restore CA1801 { if (key is null) - return ValidationError.NullParameter(nameof(key), ValidationError.GetCurrentStackFrame()); + return SignatureValidationError.NullParameter( + nameof(key), + ValidationError.GetCurrentStackFrame()); if (cryptoProviderFactory is null) - return ValidationError.NullParameter(nameof(cryptoProviderFactory), ValidationError.GetCurrentStackFrame()); + return SignatureValidationError.NullParameter( + nameof(cryptoProviderFactory), + ValidationError.GetCurrentStackFrame()); if (SignedInfo is null) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail(LogMessages.IDX30212), - ValidationFailureType.XmlValidationFailed, - typeof(XmlValidationException), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), ValidationError.GetCurrentStackFrame()); if (!cryptoProviderFactory.IsSupportedAlgorithm(SignedInfo.SignatureMethod, key)) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail(LogMessages.IDX30207, SignedInfo.SignatureMethod, cryptoProviderFactory.GetType()), ValidationFailureType.XmlValidationFailed, - typeof(XmlValidationException), + typeof(SecurityTokenInvalidSignatureException), ValidationError.GetCurrentStackFrame()); var signatureProvider = cryptoProviderFactory.CreateForVerifying(key, SignedInfo.SignatureMethod); if (signatureProvider is null) - return new XmlValidationError( + return new SignatureValidationError( new MessageDetail(LogMessages.IDX30203, cryptoProviderFactory, key, SignedInfo.SignatureMethod), ValidationFailureType.XmlValidationFailed, - typeof(XmlValidationException), + typeof(SecurityTokenInvalidSignatureException), ValidationError.GetCurrentStackFrame()); - ValidationError? validationError = null; + SignatureValidationError? validationError = null; try { @@ -170,10 +174,10 @@ public void Verify(SecurityKey key, CryptoProviderFactory cryptoProviderFactory) SignedInfo.GetCanonicalBytes(memoryStream); if (!signatureProvider.Verify(memoryStream.ToArray(), Convert.FromBase64String(SignatureValue))) { - validationError = new XmlValidationError( + validationError = new SignatureValidationError( new MessageDetail(LogMessages.IDX30200, cryptoProviderFactory, key), ValidationFailureType.XmlValidationFailed, - typeof(XmlValidationException), + typeof(SecurityTokenInvalidSignatureException), ValidationError.GetCurrentStackFrame()); } } diff --git a/src/Microsoft.IdentityModel.Xml/SignedInfo.cs b/src/Microsoft.IdentityModel.Xml/SignedInfo.cs index 9f16187e56..3ccf96df8e 100644 --- a/src/Microsoft.IdentityModel.Xml/SignedInfo.cs +++ b/src/Microsoft.IdentityModel.Xml/SignedInfo.cs @@ -118,16 +118,18 @@ public void Verify(CryptoProviderFactory cryptoProviderFactory) /// /// supplies any required cryptographic operators. /// contextual information for diagnostics. - internal ValidationError? Verify( + internal SignatureValidationError? Verify( CryptoProviderFactory cryptoProviderFactory, #pragma warning disable CA1801 CallContext callContext) #pragma warning restore CA1801 { if (cryptoProviderFactory == null) - return ValidationError.NullParameter(nameof(cryptoProviderFactory), ValidationError.GetCurrentStackFrame()); + return SignatureValidationError.NullParameter( + nameof(cryptoProviderFactory), + ValidationError.GetCurrentStackFrame()); - ValidationError? validationError = null; + SignatureValidationError? validationError = null; for (int i = 0; i < References.Count; i++) { diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Algorithm.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Algorithm.cs new file mode 100644 index 0000000000..fdfc48ed4a --- /dev/null +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Algorithm.cs @@ -0,0 +1,334 @@ +// 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 Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.JsonWebTokens.Extensibility.Tests +{ + public partial class JsonWebTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(Algorithm_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_AlgorithmValidator_Extensibility(AlgorithmExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_AlgorithmValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.ValidationError!.AddStackFrame(new StackFrame(false)); + + theoryData.ValidationParameters!.IssuerSigningKeys.Add(theoryData.SigningKey); + + try + { + ValidationResult validationResult = await theoryData.JsonWebTokenHandler.ValidateTokenAsync( + theoryData.JsonWebToken!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + // We expect the validation to fail, but it passed + context.AddDiff("ValidationResult is Valid."); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + + if (validationError is SignatureValidationError signatureValidationError && + signatureValidationError.InnerValidationError is not null) + { + IdentityComparer.AreValidationErrorsEqual( + signatureValidationError.InnerValidationError, + theoryData.ValidationError, + context); + } + else + { + IdentityComparer.AreValidationErrorsEqual( + validationError, + theoryData.ValidationError, + context); + } + + var exception = validationError.GetException(); + theoryData.ExpectedException.ProcessException(exception, context); + // Process the inner exception since invalid algorithm exceptions are wrapped inside + // invalid signature exceptions + if (theoryData.ExpectedInnerException is not null) + theoryData.ExpectedInnerException.ProcessException(exception.InnerException, context); + } + } + catch (Exception ex) + { + // We expect the validation to fail, but it threw an exception + context.AddDiff($"ValidateTokenAsync threw exception: {ex}"); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData Algorithm_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); + + #region return CustomAlgorithmValidationError + // Test cases where delegate is overridden and return a CustomAlgorithmValidationError + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(SecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 160), + "algorithm") + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorCustomExceptionDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 175), + "algorithm"), + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorUnknownExceptionDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate, + extraStackFrames: 1) + { + // CustomAlgorithmValidationError does not handle the exception type 'NotSupportedException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(NotSupportedException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate))), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 205), + "algorithm"), + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomAlgorithmValidationError.CustomAlgorithmValidationFailureType, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 190), + "algorithm"), + }); + #endregion + + #region return AlgorithmValidationError + // Test cases where delegate is overridden and return an AlgorithmValidationError + // AlgorithmValidationError : ValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(SecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate)), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 235), + "algorithm") + }); + + // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate, + extraStackFrames: 1) + { + // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenInvalidAlgorithmException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate))), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 259), + "algorithm") + }); + + // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorCustomExceptionTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate, + extraStackFrames: 1) + { + // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate))), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 274), + "algorithm") + }); + + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorThrows", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10273:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows)), + ValidationError = new SignatureValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10273), null), + ValidationFailureType.AlgorithmValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("JsonWebTokenHandler.ValidateSignature.cs", 250), + null, // no inner validation error + new CustomSecurityTokenInvalidAlgorithmException(nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows), null) + ) + }); + #endregion + + return theoryData; + } + } + + public class AlgorithmExtensibilityTheoryData : ValidateTokenAsyncBaseTheoryData + { + internal AlgorithmExtensibilityTheoryData(string testId, DateTime utcNow, AlgorithmValidationDelegate algorithmValidator, int extraStackFrames) : base(testId) + { + // The token is never read by the custom delegtes, so we create a dummy token + JsonWebToken = JsonWebTokenHandler.ReadJsonWebToken(JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor() + { + SigningCredentials = Default.SymmetricSigningCredentials, + })); + + ValidationParameters = new ValidationParameters + { + // We leave the default signature validator to call the custom algorithm validator + AlgorithmValidator = algorithmValidator, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation, + }; + + ExtraStackFrames = extraStackFrames; + } + + public JsonWebToken JsonWebToken { get; } + + public JsonWebTokenHandler JsonWebTokenHandler { get; } = new JsonWebTokenHandler(); + + public bool IsValid { get; set; } + + internal ValidationError? ValidationError { get; set; } + + public ExpectedException? ExpectedInnerException { get; set; } + + internal int ExtraStackFrames { get; } + + public SecurityKey SigningKey { get; set; } = Default.SymmetricSigningKey; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Signature.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Signature.cs new file mode 100644 index 0000000000..ac497daa72 --- /dev/null +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Signature.cs @@ -0,0 +1,266 @@ +// 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(Signature_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_SignatureValidator_Extensibility(SignatureExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_SignatureValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.SignatureValidationError!.AddStackFrame(new StackFrame(false)); + + try + { + ValidationResult validationResult = await theoryData.JsonWebTokenHandler.ValidateTokenAsync( + theoryData.JsonWebToken!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + // We expect the validation to fail, but it passed + context.AddDiff("ValidationResult is Valid."); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.SignatureValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData Signature_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); + + #region return CustomSignatureValidationError + // Test cases where delegate is overridden and return a CustomSignatureValidationError + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate, + extraStackFrames: 3) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 160)) + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorCustomExceptionDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate, + extraStackFrames: 3) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 175)), + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorUnknownExceptionDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate, + extraStackFrames: 3) + { + // CustomSignatureValidationError 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(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate))), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomSignatureValidationDelegates.cs", 205)), + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 3) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomSignatureValidationError.CustomSignatureValidationFailureType, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 190)), + }); + #endregion + + #region return SignatureValidationError + // Test cases where delegate is overridden and return an SignatureValidationError + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorDelegate, + extraStackFrames: 3) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate)), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 235)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorCustomSignatureExceptionTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate, + extraStackFrames: 3) + { + // SignatureValidationError does not handle the exception type 'CustomSecurityTokenInvalidSignatureException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate))), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 259)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorCustomExceptionTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate, + extraStackFrames: 3) + { + // SignatureValidationError 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(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate))), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomSignatureValidationDelegates.cs", 274)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorThrows", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorThrows, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + string.Format(Tokens.LogMessages.IDX10272), + typeof(CustomSecurityTokenInvalidSignatureException)), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10272), null), + ValidationFailureType.SignatureValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("JsonWebTokenHandler.ValidateSignature.cs", 250), + null, // no inner validation error + new SecurityTokenInvalidSignatureException(nameof(CustomSignatureValidationDelegates.SignatureValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } + + public class SignatureExtensibilityTheoryData : ValidateTokenAsyncBaseTheoryData + { + internal SignatureExtensibilityTheoryData(string testId, DateTime utcNow, SignatureValidationDelegate signatureValidator, int extraStackFrames) : base(testId) + { + // The token is never read by the custom delegtes, so we create a dummy token + JsonWebToken = JsonUtilities.CreateUnsignedJsonWebToken("iss", "issuer"); + + ValidationParameters = new ValidationParameters + { + AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + SignatureValidator = signatureValidator, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation + }; + + ExtraStackFrames = extraStackFrames; + } + + public JsonWebToken JsonWebToken { get; } + + public JsonWebTokenHandler JsonWebTokenHandler { get; } = new JsonWebTokenHandler(); + + public bool IsValid { get; set; } + + internal SignatureValidationError? SignatureValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ValidateTokenAsyncTests.Algorithm.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ValidateTokenAsyncTests.Algorithm.cs index 36fd83ed4a..b26524130c 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ValidateTokenAsyncTests.Algorithm.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ValidateTokenAsyncTests.Algorithm.cs @@ -73,9 +73,9 @@ public static TheoryData ValidateTokenAsy validAlgorithms: [SecurityAlgorithms.Sha256]), ExpectedIsValid = false, ExpectedException = ExpectedException.SecurityTokenInvalidSignatureException("IDX10511:"), - ExpectedExceptionValidationParameters = ExpectedException.SecurityTokenInvalidAlgorithmException( + ExpectedExceptionValidationParameters = ExpectedException.SecurityTokenInvalidSignatureException( "IDX10518:", - propertiesExpected: new() { { "InvalidAlgorithm", SecurityAlgorithms.HmacSha256Signature } }), + typeof(SecurityTokenInvalidAlgorithmException)), }); return theoryData; diff --git a/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs index 5203f6ea64..2f2bb635e7 100644 --- a/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs +++ b/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs @@ -70,7 +70,7 @@ public static class SkipValidationDelegates SecurityToken securityToken, ValidationParameters validationParameters, BaseConfiguration? configuration, - CallContext? callContext) + CallContext callContext) { // This key is not used during the validation process. It is only used to satisfy the delegate signature. // Follow up PR will change this to remove the SecurityKey return value. diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomAlgorithmValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomAlgorithmValidationDelegates.cs new file mode 100644 index 0000000000..42fa04d6de --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomAlgorithmValidationDelegates.cs @@ -0,0 +1,144 @@ +// 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 CustomAlgorithmValidationDelegates + { + internal static ValidationResult CustomAlgorithmValidatorDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + // Returns a CustomAlgorithmValidationError : AlgorithmValidationError + return new CustomAlgorithmValidationError( + new MessageDetail(nameof(CustomAlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + + internal static ValidationResult CustomAlgorithmValidatorCustomExceptionDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomAlgorithmValidationError( + new MessageDetail(nameof(CustomAlgorithmValidatorCustomExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + + internal static ValidationResult CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomAlgorithmValidationError( + new MessageDetail(nameof(CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomAlgorithmValidationError.CustomAlgorithmValidationFailureType, + typeof(CustomSecurityTokenInvalidAlgorithmException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + + internal static ValidationResult CustomAlgorithmValidatorUnknownExceptionDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomAlgorithmValidationError( + new MessageDetail(nameof(CustomAlgorithmValidatorUnknownExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(NotSupportedException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + + internal static ValidationResult CustomAlgorithmValidatorWithoutGetExceptionOverrideDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomAlgorithmWithoutGetExceptionValidationOverrideError( + new MessageDetail(nameof(CustomAlgorithmValidatorWithoutGetExceptionOverrideDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + + internal static ValidationResult AlgorithmValidatorDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new AlgorithmValidationError( + new MessageDetail(nameof(AlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + + internal static ValidationResult AlgorithmValidatorThrows( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + throw new CustomSecurityTokenInvalidAlgorithmException(nameof(AlgorithmValidatorThrows), null); + } + + internal static ValidationResult AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new AlgorithmValidationError( + new MessageDetail(nameof(AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + + internal static ValidationResult AlgorithmValidatorCustomExceptionTypeDelegate( + string algorithm, + SecurityKey securityKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new AlgorithmValidationError( + new MessageDetail(nameof(AlgorithmValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenException), + ValidationError.GetCurrentStackFrame(), + algorithm); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomSignatureValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomSignatureValidationDelegates.cs new file mode 100644 index 0000000000..f80e2e46c8 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomSignatureValidationDelegates.cs @@ -0,0 +1,127 @@ +// 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 CustomSignatureValidationDelegates + { + internal static ValidationResult CustomSignatureValidatorDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + // Returns a CustomSignatureValidationError : SignatureValidationError + return new CustomSignatureValidationError( + new MessageDetail(nameof(CustomSignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame()); + } + + internal static ValidationResult CustomSignatureValidatorCustomExceptionDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new CustomSignatureValidationError( + new MessageDetail(nameof(CustomSignatureValidatorCustomExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame()); + } + + internal static ValidationResult CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new CustomSignatureValidationError( + new MessageDetail(nameof(CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomSignatureValidationError.CustomSignatureValidationFailureType, + typeof(CustomSecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame()); + } + + internal static ValidationResult CustomSignatureValidatorUnknownExceptionDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new CustomSignatureValidationError( + new MessageDetail(nameof(CustomSignatureValidatorUnknownExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(NotSupportedException), + ValidationError.GetCurrentStackFrame()); + } + + internal static ValidationResult CustomSignatureValidatorWithoutGetExceptionOverrideDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new CustomSignatureWithoutGetExceptionValidationOverrideError( + new MessageDetail(nameof(CustomSignatureValidatorWithoutGetExceptionOverrideDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame()); + } + + internal static ValidationResult SignatureValidatorDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new SignatureValidationError( + new MessageDetail(nameof(SignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame()); + } + + internal static ValidationResult SignatureValidatorThrows( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + throw new CustomSecurityTokenInvalidSignatureException(nameof(SignatureValidatorThrows), null); + } + + internal static ValidationResult SignatureValidatorCustomSignatureExceptionTypeDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new SignatureValidationError( + new MessageDetail(nameof(SignatureValidatorCustomSignatureExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + ValidationError.GetCurrentStackFrame()); + } + + internal static ValidationResult SignatureValidatorCustomExceptionTypeDelegate( + SecurityToken? securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new SignatureValidationError( + new MessageDetail(nameof(SignatureValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenException), + ValidationError.GetCurrentStackFrame()); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs index 8f20be2168..9623161345 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs @@ -201,6 +201,98 @@ public CustomTokenTypeWithoutGetExceptionValidationOverrideError( } #endregion // TokenTypeValidationErrors + #region SignatureValidationErrors + internal class CustomSignatureValidationError : SignatureValidationError + { + /// + /// A custom validation failure type. + /// + public static readonly ValidationFailureType CustomSignatureValidationFailureType = new SignatureValidatorFailure("CustomSignatureValidationFailureType"); + private class SignatureValidatorFailure : ValidationFailureType { internal SignatureValidatorFailure(string name) : base(name) { } } + + public CustomSignatureValidationError( + MessageDetail messageDetail, + ValidationFailureType validationFailureType, + Type exceptionType, + StackFrame stackFrame, + ValidationError? innerValidationError = null, + Exception? innerException = null) : + base(messageDetail, validationFailureType, exceptionType, stackFrame, innerValidationError, innerException) + { + } + internal override Exception GetException() + { + if (ExceptionType == typeof(CustomSecurityTokenInvalidSignatureException)) + { + var exception = new CustomSecurityTokenInvalidSignatureException(MessageDetail.Message, InnerException); + exception.SetValidationError(this); + return exception; + } + return base.GetException(); + } + } + + internal class CustomSignatureWithoutGetExceptionValidationOverrideError : SignatureValidationError + { + public CustomSignatureWithoutGetExceptionValidationOverrideError( + MessageDetail messageDetail, + ValidationFailureType validationFailureType, + Type exceptionType, + StackFrame stackFrame, + ValidationError? innerValidationError = null, + Exception? innerException = null) : + base(messageDetail, validationFailureType, exceptionType, stackFrame, innerValidationError, innerException) + { + } + } + #endregion // SignatureValidationErrors + + #region AlgorithmValidationErrors + internal class CustomAlgorithmValidationError : AlgorithmValidationError + { + /// + /// A custom validation failure type. + /// + public static readonly ValidationFailureType CustomAlgorithmValidationFailureType = new AlgorithmValidatorFailure("CustomAlgorithmValidationFailureType"); + private class AlgorithmValidatorFailure : ValidationFailureType { internal AlgorithmValidatorFailure(string name) : base(name) { } } + + public CustomAlgorithmValidationError( + MessageDetail messageDetail, + ValidationFailureType validationFailureType, + Type exceptionType, + StackFrame stackFrame, + string? algorithm, + Exception? innerException = null) + : base(messageDetail, validationFailureType, exceptionType, stackFrame, algorithm, innerException) + { + } + internal override Exception GetException() + { + if (ExceptionType == typeof(CustomSecurityTokenInvalidAlgorithmException)) + { + var exception = new CustomSecurityTokenInvalidAlgorithmException(MessageDetail.Message, InnerException) { InvalidAlgorithm = InvalidAlgorithm }; + exception.SetValidationError(this); + return exception; + } + return base.GetException(); + } + } + + internal class CustomAlgorithmWithoutGetExceptionValidationOverrideError : AlgorithmValidationError + { + public CustomAlgorithmWithoutGetExceptionValidationOverrideError( + MessageDetail messageDetail, + ValidationFailureType validationFailureType, + Type exceptionType, + StackFrame stackFrame, + string? invalidAlgorithm, + Exception? innerException = null) : + base(messageDetail, validationFailureType, exceptionType, stackFrame, invalidAlgorithm, innerException) + { + } + } + #endregion // AlgorithmValidationErrors + // Other custom validation errors to be added here for signature validation, issuer signing key, etc. } #nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Algorithm.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Algorithm.cs new file mode 100644 index 0000000000..b2e3171da4 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Algorithm.cs @@ -0,0 +1,337 @@ +// 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.Logging; +using Microsoft.IdentityModel.TestUtils; + +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml2.Extensibility.Tests +{ + public partial class Saml2SecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(Algorithm_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_AlgorithmValidator_Extensibility(AlgorithmExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_AlgorithmValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.ValidationError!.AddStackFrame(new StackFrame(false)); + + Saml2SecurityToken saml2Token = (Saml2SecurityToken)theoryData.Saml2SecurityTokenHandler.CreateToken(new SecurityTokenDescriptor() + { + Issuer = Default.Issuer, + Subject = Default.SamlClaimsIdentity, + SigningCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2, + }); + theoryData.Saml2Token = theoryData.Saml2SecurityTokenHandler.ReadSaml2Token(saml2Token.Assertion.CanonicalString); + + theoryData.ValidationParameters!.IssuerSigningKeys.Add(theoryData.SigningKey); + + try + { + ValidationResult validationResult = await theoryData.Saml2SecurityTokenHandler.ValidateTokenAsync( + theoryData.Saml2Token!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + // We expect the validation to fail, but it passed + context.AddDiff("ValidationResult is Valid."); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + + if (validationError is SignatureValidationError signatureValidationError && + signatureValidationError.InnerValidationError is not null) + { + IdentityComparer.AreValidationErrorsEqual( + signatureValidationError.InnerValidationError, + theoryData.ValidationError, + context); + } + else + { + IdentityComparer.AreValidationErrorsEqual( + validationError, + theoryData.ValidationError, + context); + } + + var exception = validationError.GetException(); + theoryData.ExpectedException.ProcessException(exception, context); + // Process the inner exception since invalid algorithm exceptions are wrapped inside + // invalid signature exceptions + if (theoryData.ExpectedInnerException is not null) + theoryData.ExpectedInnerException.ProcessException(exception.InnerException, context); + } + } + catch (Exception ex) + { + // We expect the validation to fail, but it threw an exception + context.AddDiff($"ValidateTokenAsync threw exception: {ex}"); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData Algorithm_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); + + #region return CustomAlgorithmValidationError + // Test cases where delegate is overridden and return a CustomAlgorithmValidationError + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(SecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 160), + "algorithm") + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorCustomExceptionDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 175), + "algorithm"), + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorUnknownExceptionDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate, + extraStackFrames: 1) + { + // CustomAlgorithmValidationError does not handle the exception type 'NotSupportedException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(NotSupportedException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate))), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 205), + "algorithm"), + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomAlgorithmValidationError.CustomAlgorithmValidationFailureType, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 190), + "algorithm"), + }); + #endregion + + #region return AlgorithmValidationError + // Test cases where delegate is overridden and return an AlgorithmValidationError + // AlgorithmValidationError : ValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(SecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate)), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 235), + "algorithm") + }); + + // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate, + extraStackFrames: 1) + { + // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenInvalidAlgorithmException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate))), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 259), + "algorithm") + }); + + // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorCustomExceptionTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate, + extraStackFrames: 1) + { + // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate))), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 274), + "algorithm") + }); + + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorThrows", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10273:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows)), + ValidationError = new SignatureValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10273), null), + ValidationFailureType.AlgorithmValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("Saml2SecurityTokenHandler.ValidateSignature.cs", 250), + null, // no inner validation error + new CustomSecurityTokenInvalidAlgorithmException(nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows), null) + ) + }); + #endregion + + return theoryData; + } + } + + public class AlgorithmExtensibilityTheoryData : TheoryDataBase + { + internal AlgorithmExtensibilityTheoryData(string testId, DateTime utcNow, AlgorithmValidationDelegate algorithmValidator, int extraStackFrames) : base(testId) + { + ValidationParameters = new ValidationParameters + { + // We leave the default signature validator to call the custom algorithm validator + AlgorithmValidator = algorithmValidator, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation, + }; + + ExtraStackFrames = extraStackFrames; + } + + public Saml2SecurityToken? Saml2Token { get; set; } + + public Saml2SecurityTokenHandler Saml2SecurityTokenHandler { get; } = new Saml2SecurityTokenHandler(); + + public bool IsValid { get; set; } + + internal ValidationError? ValidationError { get; set; } + + public ExpectedException? ExpectedInnerException { get; set; } + + internal int ExtraStackFrames { get; } + + internal ValidationParameters ValidationParameters { get; } + + public SecurityKey SigningKey { get; set; } = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Signature.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Signature.cs new file mode 100644 index 0000000000..d1635c6635 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Signature.cs @@ -0,0 +1,270 @@ +// 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.Logging; +using Microsoft.IdentityModel.TestUtils; + +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml2.Extensibility.Tests +{ + public partial class Saml2SecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(Signature_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_SignatureValidator_Extensibility(SignatureExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_SignatureValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.SignatureValidationError!.AddStackFrame(new StackFrame(false)); + + try + { + ValidationResult validationResult = await theoryData.Saml2SecurityTokenHandler.ValidateTokenAsync( + theoryData.Saml2Token!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + // We expect the validation to fail, but it passed + context.AddDiff("ValidationResult is Valid."); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.SignatureValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData Signature_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); + + #region return CustomSignatureValidationError + // Test cases where delegate is overridden and return a CustomSignatureValidationError + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 160)) + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorCustomExceptionDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 175)), + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorUnknownExceptionDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate, + extraStackFrames: 2) + { + // CustomSignatureValidationError 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(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate))), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomSignatureValidationDelegates.cs", 205)), + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomSignatureValidationError.CustomSignatureValidationFailureType, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 190)), + }); + #endregion + + #region return SignatureValidationError + // Test cases where delegate is overridden and return an SignatureValidationError + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate)), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 235)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorCustomSignatureExceptionTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate, + extraStackFrames: 2) + { + // SignatureValidationError does not handle the exception type 'CustomSecurityTokenInvalidSignatureException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate))), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 259)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorCustomExceptionTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate, + extraStackFrames: 2) + { + // SignatureValidationError 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(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate))), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomSignatureValidationDelegates.cs", 274)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorThrows", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorThrows, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + string.Format(Tokens.LogMessages.IDX10272), + typeof(CustomSecurityTokenInvalidSignatureException)), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10272), null), + ValidationFailureType.SignatureValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("Saml2SecurityTokenHandler.ValidateSignature.cs", 250), + null, // no inner validation error + new SecurityTokenInvalidSignatureException(nameof(CustomSignatureValidationDelegates.SignatureValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } + + public class SignatureExtensibilityTheoryData : TheoryDataBase + { + internal SignatureExtensibilityTheoryData(string testId, DateTime utcNow, SignatureValidationDelegate signatureValidator, int extraStackFrames) : base(testId) + { + Saml2Token = (Saml2SecurityToken)Saml2SecurityTokenHandler.CreateToken( + new SecurityTokenDescriptor() + { + Subject = Default.SamlClaimsIdentity, + Issuer = Default.Issuer, + }); + + ValidationParameters = new ValidationParameters + { + AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + SignatureValidator = signatureValidator, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation + }; + + ExtraStackFrames = extraStackFrames; + } + + public Saml2SecurityToken Saml2Token { get; } + + public Saml2SecurityTokenHandler Saml2SecurityTokenHandler { get; } = new Saml2SecurityTokenHandler(); + + public bool IsValid { get; set; } + + internal ValidationParameters ValidationParameters { get; } + + internal SignatureValidationError? SignatureValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs index 94d38d6151..38f25ddc2a 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs @@ -132,7 +132,7 @@ static ValidationParameters CreateValidationParameters( SecurityToken token, ValidationParameters validationParameters, BaseConfiguration? configuration, - CallContext? callContext) => + CallContext callContext) => { // Set the signing key for validation token.SigningKey = issuerSigingKey; diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Algorithm.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Algorithm.cs new file mode 100644 index 0000000000..ade781d8ac --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Algorithm.cs @@ -0,0 +1,337 @@ +// 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.Logging; +using Microsoft.IdentityModel.TestUtils; + +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml.Extensibility.Tests +{ + public partial class SamlSecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(Algorithm_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_AlgorithmValidator_Extensibility(AlgorithmExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_AlgorithmValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.ValidationError!.AddStackFrame(new StackFrame(false)); + + SamlSecurityToken SamlToken = (SamlSecurityToken)theoryData.SamlSecurityTokenHandler.CreateToken(new SecurityTokenDescriptor() + { + Issuer = Default.Issuer, + Subject = Default.SamlClaimsIdentity, + SigningCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2, + }); + theoryData.SamlToken = theoryData.SamlSecurityTokenHandler.ReadSamlToken(SamlToken.Assertion.CanonicalString); + + theoryData.ValidationParameters!.IssuerSigningKeys.Add(theoryData.SigningKey); + + try + { + ValidationResult validationResult = await theoryData.SamlSecurityTokenHandler.ValidateTokenAsync( + theoryData.SamlToken!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + // We expect the validation to fail, but it passed + context.AddDiff("ValidationResult is Valid."); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + + if (validationError is SignatureValidationError signatureValidationError && + signatureValidationError.InnerValidationError is not null) + { + IdentityComparer.AreValidationErrorsEqual( + signatureValidationError.InnerValidationError, + theoryData.ValidationError, + context); + } + else + { + IdentityComparer.AreValidationErrorsEqual( + validationError, + theoryData.ValidationError, + context); + } + + var exception = validationError.GetException(); + theoryData.ExpectedException.ProcessException(exception, context); + // Process the inner exception since invalid algorithm exceptions are wrapped inside + // invalid signature exceptions + if (theoryData.ExpectedInnerException is not null) + theoryData.ExpectedInnerException.ProcessException(exception.InnerException, context); + } + } + catch (Exception ex) + { + // We expect the validation to fail, but it threw an exception + context.AddDiff($"ValidateTokenAsync threw exception: {ex}"); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData Algorithm_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); + + #region return CustomAlgorithmValidationError + // Test cases where delegate is overridden and return a CustomAlgorithmValidationError + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(SecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 160), + "algorithm") + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorCustomExceptionDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 175), + "algorithm"), + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorUnknownExceptionDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate, + extraStackFrames: 1) + { + // CustomAlgorithmValidationError does not handle the exception type 'NotSupportedException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(NotSupportedException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate))), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 205), + "algorithm"), + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomAlgorithmValidationError.CustomAlgorithmValidationFailureType, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 190), + "algorithm"), + }); + #endregion + + #region return AlgorithmValidationError + // Test cases where delegate is overridden and return an AlgorithmValidationError + // AlgorithmValidationError : ValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(SecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate)), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 235), + "algorithm") + }); + + // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate, + extraStackFrames: 1) + { + // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenInvalidAlgorithmException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate))), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 259), + "algorithm") + }); + + // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorCustomExceptionTypeDelegate", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate, + extraStackFrames: 1) + { + // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate))), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 274), + "algorithm") + }); + + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorThrows", + utcNow, + CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10273:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows)), + ValidationError = new SignatureValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10273), null), + ValidationFailureType.AlgorithmValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("SamlSecurityTokenHandler.ValidateSignature.cs", 250), + null, // no inner validation error + new CustomSecurityTokenInvalidAlgorithmException(nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows), null) + ) + }); + #endregion + + return theoryData; + } + } + + public class AlgorithmExtensibilityTheoryData : TheoryDataBase + { + internal AlgorithmExtensibilityTheoryData(string testId, DateTime utcNow, AlgorithmValidationDelegate algorithmValidator, int extraStackFrames) : base(testId) + { + ValidationParameters = new ValidationParameters + { + // We leave the default signature validator to call the custom algorithm validator + AlgorithmValidator = algorithmValidator, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation, + }; + + ExtraStackFrames = extraStackFrames; + } + + public SamlSecurityToken? SamlToken { get; set; } + + public SamlSecurityTokenHandler SamlSecurityTokenHandler { get; } = new SamlSecurityTokenHandler(); + + public bool IsValid { get; set; } + + internal ValidationError? ValidationError { get; set; } + + public ExpectedException? ExpectedInnerException { get; set; } + + internal int ExtraStackFrames { get; } + + internal ValidationParameters ValidationParameters { get; } + + public SecurityKey SigningKey { get; set; } = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Signature.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Signature.cs new file mode 100644 index 0000000000..467bce3c42 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Signature.cs @@ -0,0 +1,270 @@ +// 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.Logging; +using Microsoft.IdentityModel.TestUtils; + +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml.Extensibility.Tests +{ + public partial class SamlSecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(Signature_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_SignatureValidator_Extensibility(SignatureExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_SignatureValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.SignatureValidationError!.AddStackFrame(new StackFrame(false)); + + try + { + ValidationResult validationResult = await theoryData.SamlSecurityTokenHandler.ValidateTokenAsync( + theoryData.SamlToken!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + // We expect the validation to fail, but it passed + context.AddDiff("ValidationResult is Valid."); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.SignatureValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData Signature_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); + + #region return CustomSignatureValidationError + // Test cases where delegate is overridden and return a CustomSignatureValidationError + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 160)) + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorCustomExceptionDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 175)), + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorUnknownExceptionDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate, + extraStackFrames: 2) + { + // CustomSignatureValidationError 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(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate))), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomSignatureValidationDelegates.cs", 205)), + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate)), + SignatureValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomSignatureValidationError.CustomSignatureValidationFailureType, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 190)), + }); + #endregion + + #region return SignatureValidationError + // Test cases where delegate is overridden and return an SignatureValidationError + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate)), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 235)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorCustomSignatureExceptionTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate, + extraStackFrames: 2) + { + // SignatureValidationError does not handle the exception type 'CustomSecurityTokenInvalidSignatureException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate))), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 259)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorCustomExceptionTypeDelegate", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate, + extraStackFrames: 2) + { + // SignatureValidationError 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(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate))), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomSignatureValidationDelegates.cs", 274)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorThrows", + utcNow, + CustomSignatureValidationDelegates.SignatureValidatorThrows, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + string.Format(Tokens.LogMessages.IDX10272), + typeof(CustomSecurityTokenInvalidSignatureException)), + SignatureValidationError = new SignatureValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10272), null), + ValidationFailureType.SignatureValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("SamlSecurityTokenHandler.ValidateSignature.cs", 250), + null, // no inner validation error + new SecurityTokenInvalidSignatureException(nameof(CustomSignatureValidationDelegates.SignatureValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } + + public class SignatureExtensibilityTheoryData : TheoryDataBase + { + internal SignatureExtensibilityTheoryData(string testId, DateTime utcNow, SignatureValidationDelegate signatureValidator, int extraStackFrames) : base(testId) + { + SamlToken = (SamlSecurityToken)SamlSecurityTokenHandler.CreateToken( + new SecurityTokenDescriptor() + { + Subject = Default.SamlClaimsIdentity, + Issuer = Default.Issuer, + }); + + ValidationParameters = new ValidationParameters + { + AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + SignatureValidator = signatureValidator, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation + }; + + ExtraStackFrames = extraStackFrames; + } + + public SamlSecurityToken SamlToken { get; } + + public SamlSecurityTokenHandler SamlSecurityTokenHandler { get; } = new SamlSecurityTokenHandler(); + + public bool IsValid { get; set; } + + internal ValidationParameters ValidationParameters { get; } + + internal SignatureValidationError? SignatureValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs index f34c777f4f..3598ad2c06 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandlerTests.ValidateTokenAsyncTests.IssuerSigningKey.cs @@ -131,7 +131,7 @@ static ValidationParameters CreateValidationParameters( SecurityToken token, ValidationParameters validationParameters, BaseConfiguration? configuration, - CallContext? callContext) => + CallContext callContext) => { // Set the signing key for validation token.SigningKey = issuerSigingKey; From 8fd166c7f837cb245155d45495dee8cee203ec80 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Mon, 25 Nov 2024 22:41:31 +0000 Subject: [PATCH 06/23] Extensibility tests: Token Replay - JWT, SAML and SAML2 (#3032) * Added TokenReplayValidationError and updated the default delegate to use it * Added log message, custom validation errors, custom delegates, and validation failure type for the token replay validation extensibility tests * Handle the potential case where the token replay delegate throws * Added extensibility tests for token replay * Resolved post merge unshipped API errors raised by Visual Studio --- ...nWebTokenHandler.ValidateToken.Internal.cs | 24 +- ...rityTokenHandler.ValidateToken.Internal.cs | 31 +- ...rityTokenHandler.ValidateToken.Internal.cs | 31 +- .../InternalAPI.Unshipped.txt | 7 + .../LogMessages.cs | 1 + .../Details/TokenReplayValidationError.cs | 54 ++++ .../Validation/ValidationFailureType.cs | 7 +- .../Validation/Validators.TokenReplay.cs | 24 +- ...bTokenHandler.Extensibility.TokenReplay.cs | 277 +++++++++++++++++ .../CustomTokenReplayValidationDelegates.cs | 135 +++++++++ .../CustomValidationErrors.cs | 48 +++ ...yTokenHandler.Extensibility.TokenReplay.cs | 279 ++++++++++++++++++ ...yTokenHandler.Extensibility.TokenReplay.cs | 279 ++++++++++++++++++ 13 files changed, 1166 insertions(+), 31 deletions(-) create mode 100644 src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenReplayValidationError.cs create mode 100644 test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenReplay.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomTokenReplayValidationDelegates.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.TokenReplay.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.TokenReplay.cs diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs index ded8b16947..39b501df24 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs @@ -301,13 +301,27 @@ private async ValueTask> ValidateJWSAsync( ex); } - ValidationResult replayValidationResult = validationParameters.TokenReplayValidator( - expires, jsonWebToken.EncodedToken, validationParameters, callContext); + ValidationResult replayValidationResult; - if (!replayValidationResult.IsValid) + try { - StackFrame replayValidationFailureStackFrame = StackFrames.ReplayValidationFailed ??= new StackFrame(true); - return replayValidationResult.UnwrapError().AddStackFrame(replayValidationFailureStackFrame); + replayValidationResult = validationParameters.TokenReplayValidator( + expires, jsonWebToken.EncodedToken, validationParameters, callContext); + + if (!replayValidationResult.IsValid) + return replayValidationResult.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 TokenReplayValidationError( + new MessageDetail(TokenLogMessages.IDX10276), + ValidationFailureType.TokenReplayValidatorThrew, + typeof(SecurityTokenReplayDetectedException), + ValidationError.GetCurrentStackFrame(), + expires, + ex); } ValidationResult? actorValidationResult = null; 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 2a501e2705..3ba8835e1f 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs @@ -90,14 +90,31 @@ internal async Task> ValidateTokenAsync( if (samlToken.Assertion.Conditions is not null) { - ValidationResult tokenReplayValidationResult = Validators.ValidateTokenReplay( - samlToken.Assertion.Conditions.NotOnOrAfter, - samlToken.Assertion.CanonicalString, - validationParameters, - callContext); + ValidationResult tokenReplayValidationResult; + + try + { + tokenReplayValidationResult = validationParameters.TokenReplayValidator( + samlToken.Assertion.Conditions.NotOnOrAfter, + samlToken.Assertion.CanonicalString, + validationParameters, + callContext); - if (!tokenReplayValidationResult.IsValid) - return tokenReplayValidationResult.UnwrapError().AddCurrentStackFrame(); + if (!tokenReplayValidationResult.IsValid) + return tokenReplayValidationResult.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 TokenReplayValidationError( + new MessageDetail(Tokens.LogMessages.IDX10276), + ValidationFailureType.TokenReplayValidatorThrew, + typeof(SecurityTokenReplayDetectedException), + ValidationError.GetCurrentStackFrame(), + samlToken.Assertion.Conditions.NotOnOrAfter, + ex); + } } ValidationResult signatureValidationResult = ValidateSignature(samlToken, validationParameters, callContext); 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 8804d4d6ad..4df576ab59 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs @@ -94,14 +94,31 @@ internal async Task> ValidateTokenAsync( if (samlToken.Assertion.Conditions is not null) { - ValidationResult tokenReplayValidationResult = Validators.ValidateTokenReplay( - samlToken.Assertion.Conditions.NotOnOrAfter, - samlToken.Assertion.CanonicalString, - validationParameters, - callContext); + ValidationResult tokenReplayValidationResult; - if (!tokenReplayValidationResult.IsValid) - return tokenReplayValidationResult.UnwrapError().AddCurrentStackFrame(); + try + { + tokenReplayValidationResult = validationParameters.TokenReplayValidator( + samlToken.Assertion.Conditions.NotOnOrAfter, + samlToken.Assertion.CanonicalString, + validationParameters, + callContext); + + if (!tokenReplayValidationResult.IsValid) + return tokenReplayValidationResult.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 TokenReplayValidationError( + new MessageDetail(Tokens.LogMessages.IDX10276), + ValidationFailureType.TokenReplayValidatorThrew, + typeof(SecurityTokenReplayDetectedException), + ValidationError.GetCurrentStackFrame(), + samlToken.Assertion.Conditions.NotOnOrAfter, + ex); + } } var signatureValidationResult = ValidateSignature(samlToken, validationParameters, callContext); diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt index cbfa1e30c6..517ca0ead9 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -5,6 +5,7 @@ const Microsoft.IdentityModel.Tokens.LogMessages.IDX10272 = "IDX10272: Signature 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 const Microsoft.IdentityModel.Tokens.LogMessages.IDX10275 = "IDX10275: TokenTypeValidationDelegate threw an exception, see inner exception." -> string +const Microsoft.IdentityModel.Tokens.LogMessages.IDX10276 = "IDX10276: TokenReplayValidationDelegate 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 Microsoft.IdentityModel.Tokens.AlgorithmValidationError.InvalidAlgorithm.get -> string @@ -28,6 +29,9 @@ Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTo Microsoft.IdentityModel.Tokens.SignatureValidationError Microsoft.IdentityModel.Tokens.SignatureValidationError.InnerValidationError.get -> Microsoft.IdentityModel.Tokens.ValidationError Microsoft.IdentityModel.Tokens.SignatureValidationError.SignatureValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, Microsoft.IdentityModel.Tokens.ValidationError innerValidationError = null, System.Exception innerException = null) -> void +Microsoft.IdentityModel.Tokens.TokenReplayValidationError +Microsoft.IdentityModel.Tokens.TokenReplayValidationError.ExpirationTime.get -> System.DateTime? +Microsoft.IdentityModel.Tokens.TokenReplayValidationError.TokenReplayValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.DateTime? expirationTime, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.TokenTypeValidationError Microsoft.IdentityModel.Tokens.TokenTypeValidationError.InvalidTokenType.get -> string 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 @@ -43,8 +47,10 @@ Microsoft.IdentityModel.Tokens.ValidationResult.IsValid.get -> bool Microsoft.IdentityModel.Tokens.ValidationResult.Result.get -> TResult override Microsoft.IdentityModel.Tokens.AlgorithmValidationError.GetException() -> System.Exception override Microsoft.IdentityModel.Tokens.SignatureValidationError.GetException() -> System.Exception +override Microsoft.IdentityModel.Tokens.TokenReplayValidationError.GetException() -> System.Exception override Microsoft.IdentityModel.Tokens.TokenTypeValidationError.GetException() -> System.Exception static Microsoft.IdentityModel.Tokens.SignatureValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.SignatureValidationError +static Microsoft.IdentityModel.Tokens.TokenReplayValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.TokenReplayValidationError 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 @@ -57,5 +63,6 @@ static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.SignatureAl static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.SignatureValidatorThrew -> 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.TokenReplayValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.TokenTypeValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.XmlValidationFailed -> Microsoft.IdentityModel.Tokens.ValidationFailureType diff --git a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs index 6a203b74e7..30ce49b65e 100644 --- a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs +++ b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs @@ -91,6 +91,7 @@ internal static class LogMessages 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 IDX10275 = "IDX10275: TokenTypeValidationDelegate threw an exception, see inner exception."; + public const string IDX10276 = "IDX10276: TokenReplayValidationDelegate 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/TokenReplayValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenReplayValidationError.cs new file mode 100644 index 0000000000..02d2c53d4a --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/TokenReplayValidationError.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Diagnostics; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens +{ + internal class TokenReplayValidationError : ValidationError + { + internal TokenReplayValidationError( + MessageDetail messageDetail, + ValidationFailureType validationFailureType, + Type exceptionType, + StackFrame stackFrame, + DateTime? expirationTime, + Exception? innerException = null) + : base(messageDetail, validationFailureType, exceptionType, stackFrame, innerException) + { + ExpirationTime = expirationTime; + } + + internal override Exception GetException() + { + if (ExceptionType == typeof(SecurityTokenReplayDetectedException)) + { + SecurityTokenReplayDetectedException exception = new(MessageDetail.Message, InnerException); + exception.SetValidationError(this); + + return exception; + } + else if (ExceptionType == typeof(SecurityTokenReplayAddFailedException)) + { + SecurityTokenReplayAddFailedException exception = new(MessageDetail.Message, InnerException); + exception.SetValidationError(this); + + return exception; + } + + return base.GetException(); + } + + internal static new TokenReplayValidationError NullParameter(string parameterName, StackFrame stackFrame) => new( + MessageDetail.NullParameter(parameterName), + ValidationFailureType.NullArgument, + typeof(SecurityTokenArgumentNullException), + stackFrame, + null); + + protected DateTime? ExpirationTime { get; } + } +} +#nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs index bcdab9cfbd..9ee917bdf9 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs @@ -135,7 +135,7 @@ private class XmlValidationFailure : ValidationFailureType { internal XmlValidat public static readonly ValidationFailureType AlgorithmValidatorThrew = new AlgorithmValidationFailure("AlgorithmValidatorThrew"); /// - /// Defines a type that represents that a token is invalid. + /// Defines a type that represents the fact that the issuer validation delegate threw an exception. /// public static readonly ValidationFailureType IssuerValidatorThrew = new IssuerValidatorFailure("IssuerValidatorThrew"); private class IssuerValidatorFailure : ValidationFailureType { internal IssuerValidatorFailure(string name) : base(name) { } } @@ -145,6 +145,11 @@ private class IssuerValidatorFailure : ValidationFailureType { internal IssuerVa /// public static readonly ValidationFailureType SignatureValidatorThrew = new SignatureValidationFailure("SignatureValidatorThrew"); + /// + /// Defines a type that represents the fact that the token replay validation delegate threw an exception. + /// + public static readonly ValidationFailureType TokenReplayValidatorThrew = new TokenReplayValidationFailure("TokenReplayValidatorThrew"); + /// /// Defines a type that represents the fact that the token type validation delegate threw an exception. /// diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenReplay.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenReplay.cs index 064a6cc491..3e8de4ffd9 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenReplay.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.TokenReplay.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Diagnostics; namespace Microsoft.IdentityModel.Tokens { @@ -43,44 +42,47 @@ public static partial class Validators #pragma warning restore CA1801 // Review unused parameters { if (string.IsNullOrWhiteSpace(securityToken)) - return ValidationError.NullParameter( + return TokenReplayValidationError.NullParameter( nameof(securityToken), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); if (validationParameters == null) - return ValidationError.NullParameter( + return TokenReplayValidationError.NullParameter( nameof(validationParameters), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); // check if token if replay cache is set, then there must be an expiration time. if (validationParameters.TokenReplayCache != null) { if (expirationTime == null) - return new ValidationError( + return new TokenReplayValidationError( new MessageDetail( LogMessages.IDX10227, securityToken), ValidationFailureType.TokenReplayValidationFailed, typeof(SecurityTokenNoExpirationException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame(), + expirationTime); if (validationParameters.TokenReplayCache.TryFind(securityToken)) - return new ValidationError( + return new TokenReplayValidationError( new MessageDetail( LogMessages.IDX10228, securityToken), ValidationFailureType.TokenReplayValidationFailed, typeof(SecurityTokenReplayDetectedException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame(), + expirationTime); if (!validationParameters.TokenReplayCache.TryAdd(securityToken, expirationTime.Value)) - return new ValidationError( + return new TokenReplayValidationError( new MessageDetail( LogMessages.IDX10229, securityToken), ValidationFailureType.TokenReplayValidationFailed, typeof(SecurityTokenReplayAddFailedException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame(), + expirationTime); } // if it reaches here, that means no token replay is detected. diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenReplay.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenReplay.cs new file mode 100644 index 0000000000..3f96081d85 --- /dev/null +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenReplay.cs @@ -0,0 +1,277 @@ +// 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 Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.JsonWebTokens.Extensibility.Tests +{ + public partial class JsonWebTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(TokenReplay_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_TokenReplayValidator_Extensibility(TokenReplayExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_TokenReplayValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.TokenReplayValidationError!.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.TokenReplayValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData TokenReplay_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var expirationTime = utcNow + TimeSpan.FromHours(1); + + #region return CustomTokenReplayValidationError + // Test cases where delegate is overridden and return a CustomTokenReplayValidationError + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidationDelegate", + utcNow, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate)), + TokenReplayValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(SecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 160), + expirationTime) + }); + + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: CustomSecurityTokenReplayDetectedException : SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidatorCustomExceptionDelegate", + utcNow, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate)), + TokenReplayValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(CustomSecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 175), + expirationTime), + }); + + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidatorUnknownExceptionDelegate", + utcNow, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate, + extraStackFrames: 2) + { + // CustomTokenReplayValidationError 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(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate))), + TokenReplayValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 205), + expirationTime), + }); + + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate)), + TokenReplayValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomTokenReplayValidationError.CustomTokenReplayValidationFailureType, + typeof(CustomSecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 190), + expirationTime), + }); + #endregion + + #region return TokenReplayValidationError + // Test cases where delegate is overridden and return an TokenReplayValidationError + // TokenReplayValidationError : ValidationError, ExceptionType: SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidationDelegate", + utcNow, + CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate)), + TokenReplayValidationError = new TokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(SecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 235), + expirationTime) + }); + + // TokenReplayValidationError : ValidationError, ExceptionType: CustomSecurityTokenReplayDetectedException : SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate", + utcNow, + CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate, + extraStackFrames: 2) + { + // TokenReplayValidationError does not handle the exception type 'CustomSecurityTokenReplayDetectedException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate))), + TokenReplayValidationError = new TokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(CustomSecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 259), + expirationTime) + }); + + // TokenReplayValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidatorCustomExceptionTypeDelegate", + utcNow, + CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate, + extraStackFrames: 2) + { + // TokenReplayValidationError 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(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate))), + TokenReplayValidationError = new TokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 274), + expirationTime) + }); + + // TokenReplayValidationError : ValidationError, ExceptionType: SecurityTokenReplayDetectedException, inner: CustomSecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidatorThrows", + utcNow, + CustomTokenReplayValidationDelegates.TokenReplayValidatorThrows, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenReplayDetectedException), + string.Format(Tokens.LogMessages.IDX10276), + typeof(CustomSecurityTokenReplayDetectedException)), + TokenReplayValidationError = new TokenReplayValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10276), null), + ValidationFailureType.TokenReplayValidatorThrew, + typeof(SecurityTokenReplayDetectedException), + new StackFrame("JsonWebTokenHandler.ValidateToken.Internal.cs", 250), + expirationTime, + new SecurityTokenReplayDetectedException(nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } + + public class TokenReplayExtensibilityTheoryData : ValidateTokenAsyncBaseTheoryData + { + internal TokenReplayExtensibilityTheoryData(string testId, DateTime utcNow, TokenReplayValidationDelegate tokenReplayValidator, int extraStackFrames) : base(testId) + { + JsonWebToken = JsonWebTokenHandler.ReadJsonWebToken( + JsonWebTokenHandler.CreateToken( + new SecurityTokenDescriptor() + { + IssuedAt = utcNow, + NotBefore = utcNow, + Expires = utcNow + TimeSpan.FromHours(1), + })); + + ValidationParameters = new ValidationParameters + { + AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + SignatureValidator = SkipValidationDelegates.SkipSignatureValidation, + TokenReplayValidator = tokenReplayValidator, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation + }; + + ExtraStackFrames = extraStackFrames; + } + + public JsonWebToken JsonWebToken { get; } + + public JsonWebTokenHandler JsonWebTokenHandler { get; } = new JsonWebTokenHandler(); + + public bool IsValid { get; set; } + + internal TokenReplayValidationError? TokenReplayValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomTokenReplayValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomTokenReplayValidationDelegates.cs new file mode 100644 index 0000000000..6e10f6cba8 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomTokenReplayValidationDelegates.cs @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using Microsoft.IdentityModel.Tokens; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils +{ + internal class CustomTokenReplayValidationDelegates + { + internal static ValidationResult CustomTokenReplayValidationDelegate( + DateTime? expirationTime, + string securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + // Returns a CustomTokenReplayValidationError : IssuerValidationError + return new CustomTokenReplayValidationError( + new MessageDetail(nameof(CustomTokenReplayValidationDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(SecurityTokenReplayDetectedException), + ValidationError.GetCurrentStackFrame(), + expirationTime); + } + + internal static ValidationResult CustomTokenReplayValidatorCustomExceptionDelegate( + DateTime? expirationTime, + string securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomTokenReplayValidationError( + new MessageDetail(nameof(CustomTokenReplayValidatorCustomExceptionDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(CustomSecurityTokenReplayDetectedException), + ValidationError.GetCurrentStackFrame(), + expirationTime); + } + + internal static ValidationResult CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate( + DateTime? expirationTime, + string securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomTokenReplayValidationError( + new MessageDetail(nameof(CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomTokenReplayValidationError.CustomTokenReplayValidationFailureType, + typeof(CustomSecurityTokenReplayDetectedException), + ValidationError.GetCurrentStackFrame(), + expirationTime, + null); + } + + internal static ValidationResult CustomTokenReplayValidatorUnknownExceptionDelegate( + DateTime? expirationTime, + string securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomTokenReplayValidationError( + new MessageDetail(nameof(CustomTokenReplayValidatorUnknownExceptionDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(NotSupportedException), + ValidationError.GetCurrentStackFrame(), + expirationTime); + } + + internal static ValidationResult CustomTokenReplayValidatorWithoutGetExceptionOverrideDelegate( + DateTime? expirationTime, + string securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomTokenReplayWithoutGetExceptionValidationOverrideError( + new MessageDetail(nameof(CustomTokenReplayValidatorWithoutGetExceptionOverrideDelegate), null), + typeof(CustomSecurityTokenReplayDetectedException), + ValidationError.GetCurrentStackFrame(), + expirationTime); + } + + internal static ValidationResult TokenReplayValidationDelegate( + DateTime? expirationTime, + string securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new TokenReplayValidationError( + new MessageDetail(nameof(TokenReplayValidationDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(SecurityTokenReplayDetectedException), + ValidationError.GetCurrentStackFrame(), + expirationTime); + } + + internal static ValidationResult TokenReplayValidatorThrows( + DateTime? expirationTime, + string securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + throw new CustomSecurityTokenReplayDetectedException(nameof(TokenReplayValidatorThrows), null); + } + + internal static ValidationResult TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate( + DateTime? expirationTime, + string securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new TokenReplayValidationError( + new MessageDetail(nameof(TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(CustomSecurityTokenReplayDetectedException), + ValidationError.GetCurrentStackFrame(), + expirationTime); + } + internal static ValidationResult TokenReplayValidatorCustomExceptionTypeDelegate( + DateTime? expirationTime, + string securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new TokenReplayValidationError( + new MessageDetail(nameof(TokenReplayValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(CustomSecurityTokenException), + ValidationError.GetCurrentStackFrame(), + expirationTime); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs index 9623161345..af7361fa0b 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs @@ -293,6 +293,54 @@ public CustomAlgorithmWithoutGetExceptionValidationOverrideError( } #endregion // AlgorithmValidationErrors + #region TokenReplayValidationErrors + internal class CustomTokenReplayValidationError : TokenReplayValidationError + { + /// + /// A custom validation failure type. + /// + public static readonly ValidationFailureType CustomTokenReplayValidationFailureType = new TokenReplayValidationFailure("CustomTokenReplayValidationFailureType"); + private class TokenReplayValidationFailure : ValidationFailureType { internal TokenReplayValidationFailure(string name) : base(name) { } } + + public CustomTokenReplayValidationError( + MessageDetail messageDetail, + ValidationFailureType validationFailureType, + Type exceptionType, + StackFrame stackFrame, + DateTime? expirationTime, + Exception? innerException = null) + : base(messageDetail, validationFailureType, exceptionType, stackFrame, expirationTime, innerException) + { + } + + internal override Exception GetException() + { + if (ExceptionType == typeof(CustomSecurityTokenReplayDetectedException)) + { + var exception = new CustomSecurityTokenReplayDetectedException(MessageDetail.Message, InnerException); + exception.SetValidationError(this); + + return exception; + } + + return base.GetException(); + } + } + + internal class CustomTokenReplayWithoutGetExceptionValidationOverrideError : TokenReplayValidationError + { + public CustomTokenReplayWithoutGetExceptionValidationOverrideError( + MessageDetail messageDetail, + Type exceptionType, + StackFrame stackFrame, + DateTime? expirationTime, + Exception? innerException = null) + : base(messageDetail, ValidationFailureType.TokenReplayValidationFailed, exceptionType, stackFrame, expirationTime, innerException) + { + } + } + #endregion + // Other custom validation errors to be added here for signature validation, issuer signing key, etc. } #nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.TokenReplay.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.TokenReplay.cs new file mode 100644 index 0000000000..3266a4595a --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.TokenReplay.cs @@ -0,0 +1,279 @@ +// 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.Logging; +using Microsoft.IdentityModel.TestUtils; + +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml2.Extensibility.Tests +{ + public partial class Saml2SecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(TokenReplay_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_TokenReplayValidator_Extensibility(TokenReplayExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_TokenReplayValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.TokenReplayValidationError!.AddStackFrame(new StackFrame(false)); + + try + { + ValidationResult validationResult = await theoryData.Saml2SecurityTokenHandler.ValidateTokenAsync( + theoryData.Saml2Token!, + 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.TokenReplayValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData TokenReplay_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var expirationTime = utcNow + TimeSpan.FromHours(1); + + #region return CustomTokenReplayValidationError + // Test cases where delegate is overridden and return a CustomTokenReplayValidationError + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidationDelegate", + utcNow, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate)), + TokenReplayValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(SecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 160), + expirationTime) + }); + + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: CustomSecurityTokenReplayDetectedException : SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidatorCustomExceptionDelegate", + utcNow, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate)), + TokenReplayValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(CustomSecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 175), + expirationTime), + }); + + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidatorUnknownExceptionDelegate", + utcNow, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate, + extraStackFrames: 1) + { + // CustomTokenReplayValidationError 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(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate))), + TokenReplayValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 205), + expirationTime), + }); + + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate)), + TokenReplayValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomTokenReplayValidationError.CustomTokenReplayValidationFailureType, + typeof(CustomSecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 190), + expirationTime), + }); + #endregion + + #region return TokenReplayValidationError + // Test cases where delegate is overridden and return an TokenReplayValidationError + // TokenReplayValidationError : ValidationError, ExceptionType: SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidationDelegate", + utcNow, + CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate)), + TokenReplayValidationError = new TokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(SecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 235), + expirationTime) + }); + + // TokenReplayValidationError : ValidationError, ExceptionType: CustomSecurityTokenReplayDetectedException : SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate", + utcNow, + CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate, + extraStackFrames: 1) + { + // TokenReplayValidationError does not handle the exception type 'CustomSecurityTokenReplayDetectedException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate))), + TokenReplayValidationError = new TokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(CustomSecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 259), + expirationTime) + }); + + // TokenReplayValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidatorCustomExceptionTypeDelegate", + utcNow, + CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate, + extraStackFrames: 1) + { + // TokenReplayValidationError 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(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate))), + TokenReplayValidationError = new TokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 274), + expirationTime) + }); + + // TokenReplayValidationError : ValidationError, ExceptionType: SecurityTokenReplayDetectedException, inner: CustomSecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidatorThrows", + utcNow, + CustomTokenReplayValidationDelegates.TokenReplayValidatorThrows, + extraStackFrames: 0) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenReplayDetectedException), + string.Format(Tokens.LogMessages.IDX10276), + typeof(CustomSecurityTokenReplayDetectedException)), + TokenReplayValidationError = new TokenReplayValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10276), null), + ValidationFailureType.TokenReplayValidatorThrew, + typeof(SecurityTokenReplayDetectedException), + new StackFrame("Saml2SecurityTokenHandler.ValidateToken.Internal.cs", 250), + expirationTime, + new SecurityTokenReplayDetectedException(nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } + + public class TokenReplayExtensibilityTheoryData : TheoryDataBase + { + internal TokenReplayExtensibilityTheoryData(string testId, DateTime utcNow, TokenReplayValidationDelegate tokenReplayValidator, int extraStackFrames) : base(testId) + { + Saml2Token = (Saml2SecurityToken)Saml2SecurityTokenHandler.CreateToken( + new SecurityTokenDescriptor() + { + Subject = Default.SamlClaimsIdentity, + Issuer = Default.Issuer, + IssuedAt = utcNow, + NotBefore = utcNow, + Expires = utcNow + TimeSpan.FromHours(1), + }); + + ValidationParameters = new ValidationParameters + { + AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + SignatureValidator = SkipValidationDelegates.SkipSignatureValidation, + TokenReplayValidator = tokenReplayValidator, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation + }; + + ExtraStackFrames = extraStackFrames; + } + + public Saml2SecurityToken Saml2Token { get; } + + public Saml2SecurityTokenHandler Saml2SecurityTokenHandler { get; } = new Saml2SecurityTokenHandler(); + + public bool IsValid { get; set; } + + internal ValidationParameters? ValidationParameters { get; set; } + + internal TokenReplayValidationError? TokenReplayValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.TokenReplay.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.TokenReplay.cs new file mode 100644 index 0000000000..c603ebcacd --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.TokenReplay.cs @@ -0,0 +1,279 @@ +// 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.Logging; +using Microsoft.IdentityModel.TestUtils; + +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml.Extensibility.Tests +{ + public partial class SamlSecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(TokenReplay_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_TokenReplayValidator_Extensibility(TokenReplayExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_TokenReplayValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.TokenReplayValidationError!.AddStackFrame(new StackFrame(false)); + + try + { + ValidationResult validationResult = await theoryData.SamlSecurityTokenHandler.ValidateTokenAsync( + theoryData.SamlToken!, + 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.TokenReplayValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData TokenReplay_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var expirationTime = utcNow + TimeSpan.FromHours(1); + + #region return CustomTokenReplayValidationError + // Test cases where delegate is overridden and return a CustomTokenReplayValidationError + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidationDelegate", + utcNow, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate)), + TokenReplayValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(SecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 160), + expirationTime) + }); + + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: CustomSecurityTokenReplayDetectedException : SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidatorCustomExceptionDelegate", + utcNow, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate)), + TokenReplayValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(CustomSecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 175), + expirationTime), + }); + + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidatorUnknownExceptionDelegate", + utcNow, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate, + extraStackFrames: 1) + { + // CustomTokenReplayValidationError 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(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate))), + TokenReplayValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 205), + expirationTime), + }); + + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate)), + TokenReplayValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomTokenReplayValidationError.CustomTokenReplayValidationFailureType, + typeof(CustomSecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 190), + expirationTime), + }); + #endregion + + #region return TokenReplayValidationError + // Test cases where delegate is overridden and return an TokenReplayValidationError + // TokenReplayValidationError : ValidationError, ExceptionType: SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidationDelegate", + utcNow, + CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate)), + TokenReplayValidationError = new TokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(SecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 235), + expirationTime) + }); + + // TokenReplayValidationError : ValidationError, ExceptionType: CustomSecurityTokenReplayDetectedException : SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate", + utcNow, + CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate, + extraStackFrames: 1) + { + // TokenReplayValidationError does not handle the exception type 'CustomSecurityTokenReplayDetectedException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate))), + TokenReplayValidationError = new TokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(CustomSecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 259), + expirationTime) + }); + + // TokenReplayValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidatorCustomExceptionTypeDelegate", + utcNow, + CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate, + extraStackFrames: 1) + { + // TokenReplayValidationError 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(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate))), + TokenReplayValidationError = new TokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 274), + expirationTime) + }); + + // TokenReplayValidationError : ValidationError, ExceptionType: SecurityTokenReplayDetectedException, inner: CustomSecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidatorThrows", + utcNow, + CustomTokenReplayValidationDelegates.TokenReplayValidatorThrows, + extraStackFrames: 0) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenReplayDetectedException), + string.Format(Tokens.LogMessages.IDX10276), + typeof(CustomSecurityTokenReplayDetectedException)), + TokenReplayValidationError = new TokenReplayValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10276), null), + ValidationFailureType.TokenReplayValidatorThrew, + typeof(SecurityTokenReplayDetectedException), + new StackFrame("SamlSecurityTokenHandler.ValidateToken.Internal.cs", 250), + expirationTime, + new SecurityTokenReplayDetectedException(nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } + + public class TokenReplayExtensibilityTheoryData : TheoryDataBase + { + internal TokenReplayExtensibilityTheoryData(string testId, DateTime utcNow, TokenReplayValidationDelegate tokenReplayValidator, int extraStackFrames) : base(testId) + { + SamlToken = (SamlSecurityToken)SamlSecurityTokenHandler.CreateToken( + new SecurityTokenDescriptor() + { + Subject = Default.SamlClaimsIdentity, + Issuer = Default.Issuer, + IssuedAt = utcNow, + NotBefore = utcNow, + Expires = utcNow + TimeSpan.FromHours(1), + }); + + ValidationParameters = new ValidationParameters + { + AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + SignatureValidator = SkipValidationDelegates.SkipSignatureValidation, + TokenReplayValidator = tokenReplayValidator, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation + }; + + ExtraStackFrames = extraStackFrames; + } + + public SamlSecurityToken SamlToken { get; } + + public SamlSecurityTokenHandler SamlSecurityTokenHandler { get; } = new SamlSecurityTokenHandler(); + + public bool IsValid { get; set; } + + internal ValidationParameters? ValidationParameters { get; set; } + + internal TokenReplayValidationError? TokenReplayValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } + } +} +#nullable restore From ae57a3cbf9bcef8a1f0789010bf19cb32035d6ab Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Mon, 25 Nov 2024 23:14:13 +0000 Subject: [PATCH 07/23] Extensibility tests: Issuer signing key - JWT, SAML and SAML2 (#3029) * Added failure type and log message for the case where the issuer signing key validator throws an exception * Added IssuerSigningKeyValidationError and updated its use within the signing key validation * Removed optionality from the CallContext in the issuer signing key validation delegate, updated the test skip validation delegates (cherry picked from commit a4476e19d0577ce78a042a53ff6cd2ff874413be) * Added custom validation delegates and validation errors for issuer signing key extensibility testing (cherry picked from commit ad1e0bd9d0c044c431135ab79da9057f5264a581) * Handle the case where the issuer signing key validator throws an exception (cherry picked from commit f7b0db77fcbc5ce1d995b9392f94a08ee4bbc24a) * Added extensibility tests for issuer signing key validation (cherry picked from commit e432f8c4fbfd3eb964cad4e2580111b3a226284e) * Updated validation failure type position in tests * Apply suggestions from code review Co-authored-by: jennyf19 * Added missing SecurityKey to the legacy implementation of ValidateIssuerSigningKey --------- Co-authored-by: jennyf19 --- ...nWebTokenHandler.ValidateToken.Internal.cs | 24 +- ...rityTokenHandler.ValidateToken.Internal.cs | 33 +- ...rityTokenHandler.ValidateToken.Internal.cs | 32 +- .../InternalAPI.Unshipped.txt | 8 +- .../LogMessages.cs | 1 + .../IssuerSigningKeyValidationError.cs | 50 +++ .../Results/Details/ValidationError.cs | 6 +- .../Validation/ValidationFailureType.cs | 9 +- .../Validation/Validators.IssuerSigningKey.cs | 26 +- .../Validators.cs | 4 +- ...nHandler.Extensibility.IssuerSigningKey.cs | 284 ++++++++++++++++++ .../SkipValidationDelegates.cs | 2 +- ...stomIssuerSigningKeyValidationDelegates.cs | 150 +++++++++ .../CustomValidationErrors.cs | 46 +++ ...nHandler.Extensibility.IssuerSigningKey.cs | 283 +++++++++++++++++ ...nHandler.Extensibility.IssuerSigningKey.cs | 283 +++++++++++++++++ 16 files changed, 1198 insertions(+), 43 deletions(-) create mode 100644 src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/IssuerSigningKeyValidationError.cs create mode 100644 test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.IssuerSigningKey.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomIssuerSigningKeyValidationDelegates.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.IssuerSigningKey.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.IssuerSigningKey.cs diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs index 39b501df24..2deb10bafd 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs @@ -383,13 +383,27 @@ await ValidateJWSAsync(actorToken, actorParameters, configuration, callContext, return signatureValidationResult.UnwrapError().AddStackFrame(signatureValidationFailureStackFrame); } - ValidationResult issuerSigningKeyValidationResult = - validationParameters.IssuerSigningKeyValidator( + ValidationResult issuerSigningKeyValidationResult; + + try + { + issuerSigningKeyValidationResult = validationParameters.IssuerSigningKeyValidator( jsonWebToken.SigningKey, jsonWebToken, validationParameters, configuration, callContext); - if (!issuerSigningKeyValidationResult.IsValid) + + if (!issuerSigningKeyValidationResult.IsValid) + return issuerSigningKeyValidationResult.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 issuerSigningKeyValidationFailureStackFrame = StackFrames.IssuerSigningKeyValidationFailed ??= new StackFrame(true); - return issuerSigningKeyValidationResult.UnwrapError().AddStackFrame(issuerSigningKeyValidationFailureStackFrame); + return new IssuerSigningKeyValidationError( + new MessageDetail(TokenLogMessages.IDX10274), + ValidationFailureType.IssuerSigningKeyValidatorThrew, + typeof(SecurityTokenInvalidSigningKeyException), + ValidationError.GetCurrentStackFrame(), + jsonWebToken.SigningKey, + ex); } return new ValidatedToken(jsonWebToken, this, validationParameters) 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 3ba8835e1f..c6b14409c2 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs @@ -125,15 +125,32 @@ internal async Task> ValidateTokenAsync( return signatureValidationResult.UnwrapError().AddStackFrame(StackFrames.SignatureValidationFailed); } - ValidationResult issuerSigningKeyValidationResult = validationParameters.IssuerSigningKeyValidator( - samlToken.SigningKey, - samlToken, - validationParameters, - null, - callContext); + ValidationResult issuerSigningKeyValidationResult; + + try + { + issuerSigningKeyValidationResult = validationParameters.IssuerSigningKeyValidator( + samlToken.SigningKey, + samlToken, + validationParameters, + null, + callContext); - if (!issuerSigningKeyValidationResult.IsValid) - return issuerSigningKeyValidationResult.UnwrapError().AddCurrentStackFrame(); + if (!issuerSigningKeyValidationResult.IsValid) + return issuerSigningKeyValidationResult.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 IssuerSigningKeyValidationError( + new MessageDetail(Tokens.LogMessages.IDX10274), + ValidationFailureType.IssuerSigningKeyValidatorThrew, + typeof(SecurityTokenInvalidSigningKeyException), + ValidationError.GetCurrentStackFrame(), + samlToken.SigningKey, + ex); + } return new ValidatedToken(samlToken, this, validationParameters); } 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 4df576ab59..8da07673ef 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs @@ -128,17 +128,31 @@ internal async Task> ValidateTokenAsync( return signatureValidationResult.UnwrapError().AddStackFrame(StackFrames.SignatureValidationFailed); } - var issuerSigningKeyValidationResult = validationParameters.IssuerSigningKeyValidator( - samlToken.SigningKey, - samlToken, - validationParameters, - null, - callContext); + ValidationResult issuerSigningKeyValidationResult; - if (!issuerSigningKeyValidationResult.IsValid) + try { - StackFrames.IssuerSigningKeyValidationFailed ??= new StackFrame(true); - return issuerSigningKeyValidationResult.UnwrapError().AddStackFrame(StackFrames.IssuerSigningKeyValidationFailed); + issuerSigningKeyValidationResult = validationParameters.IssuerSigningKeyValidator( + samlToken.SigningKey, + samlToken, + validationParameters, + null, + callContext); + + if (!issuerSigningKeyValidationResult.IsValid) + return issuerSigningKeyValidationResult.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 IssuerSigningKeyValidationError( + new MessageDetail(Tokens.LogMessages.IDX10274), + ValidationFailureType.IssuerSigningKeyValidatorThrew, + typeof(SecurityTokenInvalidSigningKeyException), + ValidationError.GetCurrentStackFrame(), + samlToken.SigningKey, + ex); } return new ValidatedToken(samlToken, this, validationParameters); diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt index 517ca0ead9..c81cb4212e 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -9,11 +9,15 @@ const Microsoft.IdentityModel.Tokens.LogMessages.IDX10276 = "IDX10276: TokenRepl 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 Microsoft.IdentityModel.Tokens.AlgorithmValidationError.InvalidAlgorithm.get -> string +Microsoft.IdentityModel.Tokens.AlgorithmValidationError._invalidAlgorithm -> string Microsoft.IdentityModel.Tokens.AudienceValidationError.AudienceValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Collections.Generic.IList tokenAudiences, System.Collections.Generic.IList validAudiences, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.AudienceValidationError.TokenAudiences.get -> System.Collections.Generic.IList Microsoft.IdentityModel.Tokens.AudienceValidationError.TokenAudiences.set -> void Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidAudiences.get -> System.Collections.Generic.IList Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidAudiences.set -> void +Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError +Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.InvalidSigningKey.get -> Microsoft.IdentityModel.Tokens.SecurityKey +Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.IssuerSigningKeyValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, Microsoft.IdentityModel.Tokens.SecurityKey invalidSigningKey, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.IssuerValidationError.InvalidIssuer.get -> string Microsoft.IdentityModel.Tokens.IssuerValidationError.IssuerValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidIssuer, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedConfiguration = 1 -> Microsoft.IdentityModel.Tokens.IssuerValidationSource @@ -39,16 +43,18 @@ Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.get -> Sys Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.set -> void Microsoft.IdentityModel.Tokens.ValidationError.AddCurrentStackFrame(string filePath = "", int lineNumber = 0, int skipFrames = 1) -> Microsoft.IdentityModel.Tokens.ValidationError Microsoft.IdentityModel.Tokens.ValidationError.GetException(System.Type exceptionType, System.Exception innerException) -> System.Exception -Microsoft.IdentityModel.Tokens.ValidationError.ValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType failureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException = null) -> void +Microsoft.IdentityModel.Tokens.ValidationError.ValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.ValidationParameters.TokenTypeValidator.get -> Microsoft.IdentityModel.Tokens.TokenTypeValidationDelegate Microsoft.IdentityModel.Tokens.ValidationParameters.TokenTypeValidator.set -> void Microsoft.IdentityModel.Tokens.ValidationResult.Error.get -> Microsoft.IdentityModel.Tokens.ValidationError Microsoft.IdentityModel.Tokens.ValidationResult.IsValid.get -> bool 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.SignatureValidationError.GetException() -> System.Exception override Microsoft.IdentityModel.Tokens.TokenReplayValidationError.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.SignatureValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.SignatureValidationError static Microsoft.IdentityModel.Tokens.TokenReplayValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.TokenReplayValidationError static Microsoft.IdentityModel.Tokens.TokenTypeValidationError.NullParameter(string parameterName, System.Diagnostics.StackFrame stackFrame) -> Microsoft.IdentityModel.Tokens.TokenTypeValidationError diff --git a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs index 30ce49b65e..02062ddad4 100644 --- a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs +++ b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs @@ -90,6 +90,7 @@ internal static class LogMessages public const string IDX10269 = "IDX10269: IssuerValidationDelegate 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."; public const string IDX10275 = "IDX10275: TokenTypeValidationDelegate threw an exception, see inner exception."; public const string IDX10276 = "IDX10276: TokenReplayValidationDelegate threw an exception, see inner exception."; diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/IssuerSigningKeyValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/IssuerSigningKeyValidationError.cs new file mode 100644 index 0000000000..8f380847ae --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/IssuerSigningKeyValidationError.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Diagnostics; +using System; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens +{ + internal class IssuerSigningKeyValidationError : ValidationError + { + internal IssuerSigningKeyValidationError( + MessageDetail messageDetail, + ValidationFailureType validationFailureType, + Type exceptionType, + StackFrame stackFrame, + SecurityKey? invalidSigningKey, + Exception? innerException = null) + : base(messageDetail, validationFailureType, exceptionType, stackFrame, innerException) + { + InvalidSigningKey = invalidSigningKey; + } + + internal override Exception GetException() + { + if (ExceptionType == typeof(SecurityTokenInvalidSigningKeyException)) + { + SecurityTokenInvalidSigningKeyException? exception = new(MessageDetail.Message, InnerException) + { + SigningKey = InvalidSigningKey + }; + exception.SetValidationError(this); + + return exception; + } + + return base.GetException(); + } + + internal static new IssuerSigningKeyValidationError NullParameter(string parameterName, StackFrame stackFrame) => new( + MessageDetail.NullParameter(parameterName), + ValidationFailureType.NullArgument, + typeof(SecurityTokenArgumentNullException), + stackFrame, + null); // InvalidSigningKey + + protected SecurityKey? InvalidSigningKey { get; } + } +} +#nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs index 5347e72d65..fb8481d1ac 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs @@ -22,13 +22,13 @@ internal class ValidationError /// Creates an instance of /// /// contains information about the exception that is used to generate the exception message. - /// is the type of validation failure that occurred. + /// is the type of validation failure that occurred. /// is the type of exception that occurred. /// is the stack frame where the exception occurred. /// is the inner exception that occurred. internal ValidationError( MessageDetail messageDetail, - ValidationFailureType failureType, + ValidationFailureType validationFailureType, Type exceptionType, StackFrame stackFrame, Exception? innerException = null) @@ -36,7 +36,7 @@ internal ValidationError( InnerException = innerException; MessageDetail = messageDetail; _exceptionType = exceptionType; - FailureType = failureType; + FailureType = validationFailureType; StackFrames = new List(4) { stackFrame diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs index 9ee917bdf9..f20011641c 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs @@ -84,8 +84,8 @@ private class SignatureAlgorithmValidationFailure : ValidationFailureType { inte /// /// Defines a type that represents that signing key validation failed. /// - public static readonly ValidationFailureType SigningKeyValidationFailed = new SigningKeyValidationFailure("SigningKeyValidationFailed"); - private class SigningKeyValidationFailure : ValidationFailureType { internal SigningKeyValidationFailure(string name) : base(name) { } } + public static readonly ValidationFailureType SigningKeyValidationFailed = new IssuerSigningKeyValidationFailure("IssuerSigningKeyValidationFailed"); + private class IssuerSigningKeyValidationFailure : ValidationFailureType { internal IssuerSigningKeyValidationFailure(string name) : base(name) { } } /// /// Defines a type that represents that lifetime validation failed. @@ -140,6 +140,11 @@ 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 issuer signing key validation delegate threw an exception. + /// + public static readonly ValidationFailureType IssuerSigningKeyValidatorThrew = new IssuerSigningKeyValidationFailure("IssuerSigningKeyValidatorThrew"); + /// /// Defines a type that represents the fact that the signature validation delegate threw an exception. /// diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs index 3e27f653e4..6e4ef95132 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Diagnostics; using System.Security.Cryptography.X509Certificates; using Microsoft.IdentityModel.Logging; @@ -26,7 +25,7 @@ internal delegate ValidationResult IssuerSigningKey SecurityToken securityToken, ValidationParameters validationParameters, BaseConfiguration? configuration, - CallContext? callContext); + CallContext callContext); /// /// SigningKeyValidation @@ -55,21 +54,22 @@ internal static ValidationResult ValidateIssuerSign CallContext? callContext) { if (validationParameters == null) - return ValidationError.NullParameter( + return IssuerSigningKeyValidationError.NullParameter( nameof(validationParameters), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); if (securityKey == null) - return new ValidationError( + return new IssuerSigningKeyValidationError( new MessageDetail(LogMessages.IDX10253, nameof(securityKey)), ValidationFailureType.SigningKeyValidationFailed, typeof(SecurityTokenArgumentNullException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame(), + securityKey); if (securityToken == null) - return ValidationError.NullParameter( + return IssuerSigningKeyValidationError.NullParameter( nameof(securityToken), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame()); return ValidateIssuerSigningKeyLifeTime(securityKey, validationParameters, callContext); } @@ -98,28 +98,30 @@ internal static ValidationResult ValidateIssuerSign notAfterUtc = cert.NotAfter.ToUniversalTime(); if (notBeforeUtc > DateTimeUtil.Add(utcNow, validationParameters.ClockSkew)) - return new ValidationError( + return new IssuerSigningKeyValidationError( new MessageDetail( LogMessages.IDX10248, LogHelper.MarkAsNonPII(notBeforeUtc), LogHelper.MarkAsNonPII(utcNow)), ValidationFailureType.SigningKeyValidationFailed, typeof(SecurityTokenInvalidSigningKeyException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame(), + securityKey); //TODO: Move to CallContext //if (LogHelper.IsEnabled(EventLogLevel.Informational)) // LogHelper.LogInformation(LogMessages.IDX10250, LogHelper.MarkAsNonPII(notBeforeUtc), LogHelper.MarkAsNonPII(utcNow)); if (notAfterUtc < DateTimeUtil.Add(utcNow, validationParameters.ClockSkew.Negate())) - return new ValidationError( + return new IssuerSigningKeyValidationError( new MessageDetail( LogMessages.IDX10249, LogHelper.MarkAsNonPII(notAfterUtc), LogHelper.MarkAsNonPII(utcNow)), ValidationFailureType.SigningKeyValidationFailed, typeof(SecurityTokenInvalidSigningKeyException), - new StackFrame(true)); + ValidationError.GetCurrentStackFrame(), + securityKey); // TODO: Move to CallContext //if (LogHelper.IsEnabled(EventLogLevel.Informational)) diff --git a/src/Microsoft.IdentityModel.Tokens/Validators.cs b/src/Microsoft.IdentityModel.Tokens/Validators.cs index 157588bd51..49946550cd 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validators.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validators.cs @@ -409,13 +409,13 @@ internal static void ValidateIssuerSigningKeyLifeTime(SecurityKey securityKey, T var notAfterUtc = cert.NotAfter.ToUniversalTime(); if (notBeforeUtc > DateTimeUtil.Add(utcNow, validationParameters.ClockSkew)) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSigningKeyException(LogHelper.FormatInvariant(LogMessages.IDX10248, LogHelper.MarkAsNonPII(notBeforeUtc), LogHelper.MarkAsNonPII(utcNow)))); + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSigningKeyException(LogHelper.FormatInvariant(LogMessages.IDX10248, LogHelper.MarkAsNonPII(notBeforeUtc), LogHelper.MarkAsNonPII(utcNow))) { SigningKey = securityKey }); if (LogHelper.IsEnabled(EventLogLevel.Informational)) LogHelper.LogInformation(LogMessages.IDX10250, LogHelper.MarkAsNonPII(notBeforeUtc), LogHelper.MarkAsNonPII(utcNow)); if (notAfterUtc < DateTimeUtil.Add(utcNow, validationParameters.ClockSkew.Negate())) - throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSigningKeyException(LogHelper.FormatInvariant(LogMessages.IDX10249, LogHelper.MarkAsNonPII(notAfterUtc), LogHelper.MarkAsNonPII(utcNow)))); + throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidSigningKeyException(LogHelper.FormatInvariant(LogMessages.IDX10249, LogHelper.MarkAsNonPII(notAfterUtc), LogHelper.MarkAsNonPII(utcNow))) { SigningKey = securityKey }); if (LogHelper.IsEnabled(EventLogLevel.Informational)) LogHelper.LogInformation(LogMessages.IDX10251, LogHelper.MarkAsNonPII(notAfterUtc), LogHelper.MarkAsNonPII(utcNow)); diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.IssuerSigningKey.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.IssuerSigningKey.cs new file mode 100644 index 0000000000..3b2bd111c6 --- /dev/null +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.IssuerSigningKey.cs @@ -0,0 +1,284 @@ +// 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 Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.JsonWebTokens.Extensibility.Tests +{ + public partial class JsonWebTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(IssuerSigningKey_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility(IssuerSigningKeyExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.IssuerSigningKeyValidationError!.AddStackFrame(new StackFrame(false)); + + try + { + ValidationResult validationResult = await theoryData.JsonWebTokenHandler.ValidateTokenAsync( + theoryData.JsonWebToken!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + context.AddDiff("validationResult.IsValid is true, expected false"); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.IssuerSigningKeyValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData IssuerSigningKey_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); + + #region return CustomIssuerSigningKeyValidationError + // Test cases where delegate is overridden and return a CustomIssuerSigningKeyValidationError + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate)), + IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(SecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 160), + null) + }); + + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerSigningKeyException : SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorCustomExceptionDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate)), + IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(CustomSecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 175), + null), + }); + + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorUnknownExceptionDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate, + extraStackFrames: 2) + { + // CustomIssuerSigningKeyValidationError 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(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate))), + IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 205), + null), + }); + + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate)), + IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomIssuerSigningKeyValidationError.CustomIssuerSigningKeyValidationFailureType, + typeof(CustomSecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 190), + null), + }); + #endregion + + #region return IssuerSigningKeyValidationError + // Test cases where delegate is overridden and return an IssuerSigningKeyValidationError + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate, + extraStackFrames: 2) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate)), + IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(SecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 235), + null) + }); + + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerSigningKeyException : SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate, + extraStackFrames: 2) + { + // IssuerSigningKeyValidationError does not handle the exception type 'CustomSecurityTokenInvalidIssuerSigningKeyException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate))), + IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(CustomSecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 259), + null) + }); + + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorCustomExceptionTypeDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate, + extraStackFrames: 2) + { + // IssuerSigningKeyValidationError 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(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate))), + IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 274), + null) + }); + + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException, inner: CustomSecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorThrows", + utcNow, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorThrows, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSigningKeyException), + string.Format(Tokens.LogMessages.IDX10274), + typeof(CustomSecurityTokenInvalidSigningKeyException)), + IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10274), null), + ValidationFailureType.IssuerSigningKeyValidatorThrew, + typeof(SecurityTokenInvalidSigningKeyException), + new StackFrame("JsonWebTokenHandler.ValidateToken.Internal.cs", 250), + null, + new SecurityTokenInvalidSigningKeyException(nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } + + public class IssuerSigningKeyExtensibilityTheoryData : ValidateTokenAsyncBaseTheoryData + { + internal IssuerSigningKeyExtensibilityTheoryData(string testId, DateTime utcNow, IssuerSigningKeyValidationDelegate issuerSigningKeyValidator, int extraStackFrames) : base(testId) + { + JsonWebToken = JsonWebTokenHandler.ReadJsonWebToken( + JsonWebTokenHandler.CreateToken( + new SecurityTokenDescriptor() + { + IssuedAt = utcNow, + NotBefore = utcNow, + Expires = utcNow + TimeSpan.FromHours(1), + })); + + ValidationParameters = new ValidationParameters + { + AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = issuerSigningKeyValidator, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + SignatureValidator = (SecurityToken token, ValidationParameters validationParameters, BaseConfiguration? configuration, CallContext callContext) => + { + token.SigningKey = SigningKey; + + return SigningKey; + }, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation + }; + + ExtraStackFrames = extraStackFrames; + } + + public JsonWebToken JsonWebToken { get; } + + public JsonWebTokenHandler JsonWebTokenHandler { get; } = new JsonWebTokenHandler(); + + public bool IsValid { get; set; } + + public SecurityKey SigningKey { get; set; } = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key; + + internal IssuerSigningKeyValidationError? IssuerSigningKeyValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs index 2f2bb635e7..90b49be8aa 100644 --- a/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs +++ b/test/Microsoft.IdentityModel.TestUtils/SkipValidationDelegates.cs @@ -48,7 +48,7 @@ public static class SkipValidationDelegates SecurityToken securityToken, ValidationParameters validationParameters, BaseConfiguration? configuration, - CallContext? callContext) + CallContext callContext) { return new ValidatedSigningKeyLifetime( null, // ValidFrom diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomIssuerSigningKeyValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomIssuerSigningKeyValidationDelegates.cs new file mode 100644 index 0000000000..2c94cdaf92 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomIssuerSigningKeyValidationDelegates.cs @@ -0,0 +1,150 @@ +// 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 CustomIssuerSigningKeyValidationDelegates + { + internal static ValidationResult CustomIssuerSigningKeyValidatorDelegate( + SecurityKey signingKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + // Returns a CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError + return new CustomIssuerSigningKeyValidationError( + new MessageDetail(nameof(CustomIssuerSigningKeyValidatorDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(SecurityTokenInvalidSigningKeyException), + ValidationError.GetCurrentStackFrame(), + signingKey, + null); + } + + internal static ValidationResult CustomIssuerSigningKeyValidatorCustomExceptionDelegate( + SecurityKey signingKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new CustomIssuerSigningKeyValidationError( + new MessageDetail(nameof(CustomIssuerSigningKeyValidatorCustomExceptionDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(CustomSecurityTokenInvalidSigningKeyException), + ValidationError.GetCurrentStackFrame(), + signingKey, + null); + } + + internal static ValidationResult CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate( + SecurityKey signingKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new CustomIssuerSigningKeyValidationError( + new MessageDetail(nameof(CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomIssuerSigningKeyValidationError.CustomIssuerSigningKeyValidationFailureType, + typeof(CustomSecurityTokenInvalidSigningKeyException), + ValidationError.GetCurrentStackFrame(), + signingKey); + } + + internal static ValidationResult CustomIssuerSigningKeyValidatorUnknownExceptionDelegate( + SecurityKey signingKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new CustomIssuerSigningKeyValidationError( + new MessageDetail(nameof(CustomIssuerSigningKeyValidatorUnknownExceptionDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(NotSupportedException), + ValidationError.GetCurrentStackFrame(), + signingKey, + null); + } + + internal static ValidationResult CustomIssuerSigningKeyValidatorWithoutGetExceptionOverrideDelegate( + SecurityKey signingKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new CustomIssuerSigningKeyWithoutGetExceptionValidationOverrideError( + new MessageDetail(nameof(CustomIssuerSigningKeyValidatorWithoutGetExceptionOverrideDelegate), null), + typeof(CustomSecurityTokenInvalidSigningKeyException), + ValidationError.GetCurrentStackFrame(), + signingKey, + null); + } + + internal static ValidationResult IssuerSigningKeyValidatorDelegate( + SecurityKey signingKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new IssuerSigningKeyValidationError( + new MessageDetail(nameof(IssuerSigningKeyValidatorDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(SecurityTokenInvalidSigningKeyException), + ValidationError.GetCurrentStackFrame(), + signingKey, + null); + } + + internal static ValidationResult IssuerSigningKeyValidatorThrows( + SecurityKey signingKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + throw new CustomSecurityTokenInvalidSigningKeyException(nameof(IssuerSigningKeyValidatorThrows), null); + } + + internal static ValidationResult IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate( + SecurityKey signingKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new IssuerSigningKeyValidationError( + new MessageDetail(nameof(IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(CustomSecurityTokenInvalidSigningKeyException), + ValidationError.GetCurrentStackFrame(), + signingKey, + null); + } + + internal static ValidationResult IssuerSigningKeyValidatorCustomExceptionTypeDelegate( + SecurityKey signingKey, + SecurityToken securityToken, + ValidationParameters validationParameters, + BaseConfiguration? configuration, + CallContext callContext) + { + return new IssuerSigningKeyValidationError( + new MessageDetail(nameof(IssuerSigningKeyValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(CustomSecurityTokenException), + ValidationError.GetCurrentStackFrame(), + signingKey, + null); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs index af7361fa0b..7571d4f8aa 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomValidationErrors.cs @@ -156,6 +156,52 @@ public CustomLifetimeWithoutGetExceptionValidationOverrideError( } #endregion + #region IssuerSigningKeyValidationErrors + internal class CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError + { + /// + /// A custom validation failure type. + /// + public static readonly ValidationFailureType CustomIssuerSigningKeyValidationFailureType = new IssuerSigningKeyValidationFailure("CustomIssuerSigningKeyValidationFailureType"); + private class IssuerSigningKeyValidationFailure : ValidationFailureType { internal IssuerSigningKeyValidationFailure(string name) : base(name) { } } + + public CustomIssuerSigningKeyValidationError( + MessageDetail messageDetail, + ValidationFailureType validationFailureType, + Type exceptionType, + StackFrame stackFrame, + SecurityKey? securityKey, + Exception? innerException = null) + : base(messageDetail, validationFailureType, exceptionType, stackFrame, securityKey, innerException) + { + } + + internal override Exception GetException() + { + if (ExceptionType == typeof(CustomSecurityTokenInvalidSigningKeyException)) + { + var exception = new CustomSecurityTokenInvalidSigningKeyException(MessageDetail.Message, InnerException) { SigningKey = InvalidSigningKey }; + exception.SetValidationError(this); + return exception; + } + return base.GetException(); + } + } + + internal class CustomIssuerSigningKeyWithoutGetExceptionValidationOverrideError : IssuerSigningKeyValidationError + { + public CustomIssuerSigningKeyWithoutGetExceptionValidationOverrideError( + MessageDetail messageDetail, + Type exceptionType, + StackFrame stackFrame, + SecurityKey? securityKey, + Exception? innerException = null) + : base(messageDetail, ValidationFailureType.SigningKeyValidationFailed, exceptionType, stackFrame, securityKey, innerException) + { + } + } + #endregion // IssuerSigningKeyValidationErrors + #region TokenTypeValidationErrors internal class CustomTokenTypeValidationError : TokenTypeValidationError { diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.IssuerSigningKey.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.IssuerSigningKey.cs new file mode 100644 index 0000000000..95c1eecfd6 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.IssuerSigningKey.cs @@ -0,0 +1,283 @@ +// 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.Logging; +using Microsoft.IdentityModel.TestUtils; + +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml2.Extensibility.Tests +{ + public partial class Saml2SecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(IssuerSigningKey_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility(IssuerSigningKeyExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.IssuerSigningKeyValidationError!.AddStackFrame(new StackFrame(false)); + + try + { + ValidationResult validationResult = await theoryData.Saml2SecurityTokenHandler.ValidateTokenAsync( + theoryData.Saml2Token!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + context.AddDiff("validationResult.IsValid is true, expected false"); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.IssuerSigningKeyValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData IssuerSigningKey_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); + + #region return CustomIssuerSigningKeyValidationError + // Test cases where delegate is overridden and return a CustomIssuerSigningKeyValidationError + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate)), + IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(SecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 160), + null) + }); + + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerSigningKeyException : SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorCustomExceptionDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate)), + IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(CustomSecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 175), + null) + }); + + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorUnknownExceptionDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate, + extraStackFrames: 1) + { + // CustomIssuerSigningKeyValidationError 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(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate))), + IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 205), + null) + }); + + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate)), + IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomIssuerSigningKeyValidationError.CustomIssuerSigningKeyValidationFailureType, + typeof(CustomSecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 190), + null), + }); + #endregion + + #region return IssuerSigningKeyValidationError + // Test cases where delegate is overridden and return an IssuerSigningKeyValidationError + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate)), + IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(SecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 235), + null) + }); + + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerSigningKeyException : SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate, + extraStackFrames: 1) + { + // IssuerSigningKeyValidationError does not handle the exception type 'CustomSecurityTokenInvalidIssuerSigningKeyException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate))), + IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(CustomSecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 259), + null) + }); + + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorCustomExceptionTypeDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate, + extraStackFrames: 1) + { + // IssuerSigningKeyValidationError 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(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate))), + IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 274), + null) + }); + + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException, inner: CustomSecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorThrows", + utcNow, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorThrows, + extraStackFrames: 0) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSigningKeyException), + string.Format(Tokens.LogMessages.IDX10274), + typeof(CustomSecurityTokenInvalidSigningKeyException)), + IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10274), null), + ValidationFailureType.IssuerSigningKeyValidatorThrew, + typeof(SecurityTokenInvalidSigningKeyException), + new StackFrame("Saml2SecurityTokenHandler.ValidateToken.Internal.cs", 250), + null, + new SecurityTokenInvalidSigningKeyException(nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } + + public class IssuerSigningKeyExtensibilityTheoryData : TheoryDataBase + { + internal IssuerSigningKeyExtensibilityTheoryData(string testId, DateTime utcNow, IssuerSigningKeyValidationDelegate issuerSigningKeyValidator, int extraStackFrames) : base(testId) + { + Saml2Token = (Saml2SecurityToken)Saml2SecurityTokenHandler.CreateToken( + new SecurityTokenDescriptor() + { + Subject = Default.SamlClaimsIdentity, + Issuer = Default.Issuer, + }); + + ValidationParameters = new ValidationParameters + { + AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = issuerSigningKeyValidator, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + SignatureValidator = (SecurityToken token, ValidationParameters validationParameters, BaseConfiguration? configuration, CallContext callContext) => + { + token.SigningKey = SigningKey; + + return SigningKey; + }, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation + }; + + ExtraStackFrames = extraStackFrames; + } + + public Saml2SecurityToken Saml2Token { get; } + + public Saml2SecurityTokenHandler Saml2SecurityTokenHandler { get; } = new Saml2SecurityTokenHandler(); + + public bool IsValid { get; set; } + + public SecurityKey SigningKey { get; set; } = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key; + + internal ValidationParameters? ValidationParameters { get; set; } + + internal IssuerSigningKeyValidationError? IssuerSigningKeyValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.IssuerSigningKey.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.IssuerSigningKey.cs new file mode 100644 index 0000000000..a6d68dda0e --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.IssuerSigningKey.cs @@ -0,0 +1,283 @@ +// 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.Logging; +using Microsoft.IdentityModel.TestUtils; + +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml.Extensibility.Tests +{ + public partial class SamlSecurityTokenHandlerValidateTokenAsyncTests + { + [Theory, MemberData(nameof(IssuerSigningKey_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility(IssuerSigningKeyExtensibilityTheoryData theoryData) + { + var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility)}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.IssuerSigningKeyValidationError!.AddStackFrame(new StackFrame(false)); + + try + { + ValidationResult validationResult = await theoryData.SamlSecurityTokenHandler.ValidateTokenAsync( + theoryData.SamlToken!, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + context.AddDiff("validationResult.IsValid is true, expected false"); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.IssuerSigningKeyValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + theoryData.ExpectedException.ProcessException(ex, context); + } + + TestUtilities.AssertFailIfErrors(context); + } + + public static TheoryData IssuerSigningKey_ExtensibilityTestCases + { + get + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + var utcNow = DateTime.UtcNow; + var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); + + #region return CustomIssuerSigningKeyValidationError + // Test cases where delegate is overridden and return a CustomIssuerSigningKeyValidationError + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate)), + IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(SecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 160), + null) + }); + + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerSigningKeyException : SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorCustomExceptionDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate)), + IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(CustomSecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 175), + null) + }); + + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorUnknownExceptionDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate, + extraStackFrames: 1) + { + // CustomIssuerSigningKeyValidationError 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(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate))), + IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 205), + null) + }); + + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate)), + IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomIssuerSigningKeyValidationError.CustomIssuerSigningKeyValidationFailureType, + typeof(CustomSecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 190), + null), + }); + #endregion + + #region return IssuerSigningKeyValidationError + // Test cases where delegate is overridden and return an IssuerSigningKeyValidationError + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate, + extraStackFrames: 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate)), + IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(SecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 235), + null) + }); + + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerSigningKeyException : SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate, + extraStackFrames: 1) + { + // IssuerSigningKeyValidationError does not handle the exception type 'CustomSecurityTokenInvalidIssuerSigningKeyException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate))), + IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(CustomSecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 259), + null) + }); + + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorCustomExceptionTypeDelegate", + utcNow, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate, + extraStackFrames: 1) + { + // IssuerSigningKeyValidationError 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(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate))), + IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 274), + null) + }); + + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException, inner: CustomSecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorThrows", + utcNow, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorThrows, + extraStackFrames: 0) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSigningKeyException), + string.Format(Tokens.LogMessages.IDX10274), + typeof(CustomSecurityTokenInvalidSigningKeyException)), + IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10274), null), + ValidationFailureType.IssuerSigningKeyValidatorThrew, + typeof(SecurityTokenInvalidSigningKeyException), + new StackFrame("SamlSecurityTokenHandler.ValidateToken.Internal.cs", 250), + null, + new SecurityTokenInvalidSigningKeyException(nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } + + public class IssuerSigningKeyExtensibilityTheoryData : TheoryDataBase + { + internal IssuerSigningKeyExtensibilityTheoryData(string testId, DateTime utcNow, IssuerSigningKeyValidationDelegate issuerSigningKeyValidator, int extraStackFrames) : base(testId) + { + SamlToken = (SamlSecurityToken)SamlSecurityTokenHandler.CreateToken( + new SecurityTokenDescriptor() + { + Subject = Default.SamlClaimsIdentity, + Issuer = Default.Issuer, + }); + + ValidationParameters = new ValidationParameters + { + AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, + AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, + IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, + IssuerSigningKeyValidator = issuerSigningKeyValidator, + LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, + SignatureValidator = (SecurityToken token, ValidationParameters validationParameters, BaseConfiguration? configuration, CallContext callContext) => + { + token.SigningKey = SigningKey; + + return SigningKey; + }, + TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, + TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation + }; + + ExtraStackFrames = extraStackFrames; + } + + public SamlSecurityToken SamlToken { get; } + + public SamlSecurityTokenHandler SamlSecurityTokenHandler { get; } = new SamlSecurityTokenHandler(); + + public bool IsValid { get; set; } + + public SecurityKey SigningKey { get; set; } = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key; + + internal ValidationParameters? ValidationParameters { get; set; } + + internal IssuerSigningKeyValidationError? IssuerSigningKeyValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } + } +} +#nullable restore From eb0daac7e033bd011ceae82f2140fdf3101b53cf Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Thu, 28 Nov 2024 03:56:59 +0000 Subject: [PATCH 08/23] Avoid code duplication in extensibility testing (#3041) * Modified ValidateTokenAsync in SAML and SAML2 token handlers to receive SecurityToken instead of Saml(2)SecurityToken * Added test interface to unify JsonWebTokenHandler, SamlSecurityTokenHandler and Saml2SecurityTokenHandler's ValidateTokenAsync and CreateToken methods under a shared API * Added ExtensibilityTheoryData as the base for all extensibility theory data. Added ValidateTokenAsyncExtensibility to test all extensibility aspects across JWT, SAML, and SAML2 * Added issuer specific theory data and extensibility test cases * Removed all duplicated code in issuer extensibility testing across JWT, SAML, and SAML2 using the new test classes * Apply suggestions from code review Co-authored-by: msbw2 * Apply suggestions from code review --------- Co-authored-by: msbw2 --- .../InternalAPI.Unshipped.txt | 4 +- .../InternalsVisibleTo.cs | 5 + ...rityTokenHandler.ValidateToken.Internal.cs | 19 +- ...rityTokenHandler.ValidateToken.Internal.cs | 19 +- ...sonWebTokenHandler.Extensibility.Issuer.cs | 273 ++--------------- .../Tests/ExtensibilityTheoryData.cs | 88 ++++++ .../IExtensibilityTestingTokenHandler.cs | 95 ++++++ .../Tests/IssuerExtensibilityTestCases.cs | 205 +++++++++++++ .../Tests/IssuerExtensibilityTheoryData.cs | 25 ++ .../Tests/ValidateTokenAsyncExtensibility.cs | 50 ++++ ...curityTokenHandler.Extensibility.Issuer.cs | 277 ++---------------- ...curityTokenHandler.Extensibility.Issuer.cs | 277 ++---------------- 12 files changed, 556 insertions(+), 781 deletions(-) create mode 100644 src/Microsoft.IdentityModel.Tokens.Saml/InternalsVisibleTo.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IExtensibilityTestingTokenHandler.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTestCases.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTheoryData.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ValidateTokenAsyncExtensibility.cs diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt index db8578df07..6e9837ef35 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt @@ -8,12 +8,12 @@ Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedConditions(string ValidatedAudience, Microsoft.IdentityModel.Tokens.ValidatedLifetime? ValidatedLifetime) -> void Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedLifetime.get -> Microsoft.IdentityModel.Tokens.ValidatedLifetime? Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedLifetime.set -> void -Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> Microsoft.IdentityModel.Tokens.Saml.SamlValidationError Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.SamlValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames -Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> +Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.Saml2ValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException = null) -> void diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/InternalsVisibleTo.cs b/src/Microsoft.IdentityModel.Tokens.Saml/InternalsVisibleTo.cs new file mode 100644 index 0000000000..6918991a28 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens.Saml/InternalsVisibleTo.cs @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Microsoft.IdentityModel.TestUtils, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] + 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 c6b14409c2..23b4444503 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs @@ -35,21 +35,34 @@ internal async Task> ValidateTokenAsync( } internal async Task> ValidateTokenAsync( - SamlSecurityToken samlToken, + SecurityToken securityToken, ValidationParameters validationParameters, CallContext callContext, #pragma warning disable CA1801 // Review unused parameters CancellationToken cancellationToken) #pragma warning restore CA1801 // Review unused parameters { - if (samlToken is null) + if (securityToken is null) { StackFrames.TokenNull ??= new StackFrame(true); return ValidationError.NullParameter( - nameof(samlToken), + nameof(securityToken), StackFrames.TokenNull); } + if (securityToken is not SamlSecurityToken samlToken) + { + return new ValidationError( + new MessageDetail( + LogMessages.IDX11400, + this, + typeof(SamlSecurityToken), + securityToken.GetType()), + ValidationFailureType.InvalidSecurityToken, + typeof(SecurityTokenArgumentException), + ValidationError.GetCurrentStackFrame()); + } + if (validationParameters is null) { StackFrames.TokenValidationParametersNull ??= new StackFrame(true); 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 8da07673ef..81ae5762f8 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs @@ -36,19 +36,32 @@ internal async Task> ValidateTokenAsync( } internal async Task> ValidateTokenAsync( - Saml2SecurityToken samlToken, + SecurityToken securityToken, ValidationParameters validationParameters, CallContext callContext, CancellationToken cancellationToken) { - if (samlToken is null) + if (securityToken is null) { StackFrames.TokenNull ??= new StackFrame(true); return ValidationError.NullParameter( - nameof(samlToken), + nameof(securityToken), StackFrames.TokenNull); } + if (securityToken is not Saml2SecurityToken samlToken) + { + return new ValidationError( + new MessageDetail( + Tokens.Saml.LogMessages.IDX11400, + this, + typeof(Saml2SecurityToken), + securityToken.GetType()), + ValidationFailureType.InvalidSecurityToken, + typeof(SecurityTokenArgumentException), + ValidationError.GetCurrentStackFrame()); + } + if (validationParameters is null) { StackFrames.TokenValidationParametersNull ??= new StackFrame(true); diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Issuer.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Issuer.cs index 7f703bb951..37bf08994d 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Issuer.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Issuer.cs @@ -1,15 +1,8 @@ // 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 Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -17,257 +10,27 @@ namespace Microsoft.IdentityModel.JsonWebTokens.Extensibility.Tests { public partial class JsonWebTokenHandlerValidateTokenAsyncTests { - [Theory, MemberData(nameof(Issuer_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateTokenAsync_IssuerValidator_Extensibility(IssuerExtensibilityTheoryData theoryData) + [Theory, MemberData( + nameof(GenerateIssuerExtensibilityTestCases), + parameters: ["JWT", 2], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_IssuerValidator_Extensibility( + IssuerExtensibilityTheoryData theoryData) { - var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_IssuerValidator_Extensibility)}", theoryData); - context.IgnoreType = false; - for (int i = 0; i < theoryData.ExtraStackFrames; i++) - theoryData.IssuerValidationError!.AddStackFrame(new StackFrame(false)); - - try - { - ValidationResult validationResult = await theoryData.JsonWebTokenHandler.ValidateTokenAsync( - theoryData.JsonWebToken!, - theoryData.ValidationParameters!, - theoryData.CallContext, - CancellationToken.None); - - if (validationResult.IsValid) - { - ValidatedToken validatedToken = validationResult.UnwrapResult(); - if (validatedToken.ValidatedIssuer.HasValue) - IdentityComparer.AreValidatedIssuersEqual(validatedToken.ValidatedIssuer.Value, theoryData.ValidatedIssuer, context); - } - else - { - ValidationError validationError = validationResult.UnwrapError(); - IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.IssuerValidationError, context); - theoryData.ExpectedException.ProcessException(validationError.GetException(), context); - } - } - catch (Exception ex) - { - theoryData.ExpectedException.ProcessException(ex, context); - } - - TestUtilities.AssertFailIfErrors(context); - } - - public static TheoryData Issuer_ExtensibilityTestCases - { - get - { - var theoryData = new TheoryData(); - CallContext callContext = new CallContext(); - string issuerGuid = Guid.NewGuid().ToString(); - - #region return CustomIssuerValidationError - // Test cases where delegate is overridden and return an CustomIssuerValidationError - // CustomIssuerValidationError : IssuerValidationError, ExceptionType: SecurityTokenInvalidIssuerException - theoryData.Add(new IssuerExtensibilityTheoryData( - "CustomIssuerValidatorDelegate", - issuerGuid, - CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidIssuerException), - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync)), - IssuerValidationError = new CustomIssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(SecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates.cs", 88), - issuerGuid) - }); - - // CustomIssuerValidationError : IssuerValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerException : SecurityTokenInvalidIssuerException - theoryData.Add(new IssuerExtensibilityTheoryData( - "CustomIssuerValidatorCustomExceptionDelegate", - issuerGuid, - CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidIssuerException), - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync)), - IssuerValidationError = new CustomIssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(CustomSecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates.cs", 107), - issuerGuid), - }); - - // CustomIssuerValidationError : IssuerValidationError, ExceptionType: NotSupportedException : SystemException - theoryData.Add(new IssuerExtensibilityTheoryData( - "CustomIssuerValidatorUnknownExceptionDelegate", - issuerGuid, - CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync, - extraStackFrames: 2) - { - // CustomIssuerValidationError 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(CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync))), - IssuerValidationError = new CustomIssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(NotSupportedException), - new StackFrame("CustomIssuerValidationDelegates.cs", 139), - issuerGuid), - }); - - // CustomIssuerValidationError : IssuerValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomIssuerValidationFailureType - theoryData.Add(new IssuerExtensibilityTheoryData( - "CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegate", - issuerGuid, - CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidIssuerException), - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync)), - IssuerValidationError = new CustomIssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync), null), - CustomIssuerValidationError.CustomIssuerValidationFailureType, - typeof(CustomSecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates.cs", 123), - issuerGuid, - null), - }); - #endregion - - #region return IssuerValidationError - // Test cases where delegate is overridden and return an IssuerValidationError - // IssuerValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerException - theoryData.Add(new IssuerExtensibilityTheoryData( - "IssuerValidatorDelegate", - issuerGuid, - CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidIssuerException), - nameof(CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync)), - IssuerValidationError = new IssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(SecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates.cs", 169), - issuerGuid) - }); - - // IssuerValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerException : SecurityTokenInvalidIssuerException - theoryData.Add(new IssuerExtensibilityTheoryData( - "IssuerValidatorCustomIssuerExceptionTypeDelegate", - issuerGuid, - CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync, - extraStackFrames: 2) - { - // IssuerValidationError does not handle the exception type 'CustomSecurityTokenInvalidIssuerException' - ExpectedException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenInvalidIssuerException), - nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync))), - IssuerValidationError = new IssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(CustomSecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates.cs", 196), - issuerGuid) - }); - - // IssuerValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException - theoryData.Add(new IssuerExtensibilityTheoryData( - "IssuerValidatorCustomExceptionTypeDelegate", - issuerGuid, - CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync, - extraStackFrames: 2) - { - // IssuerValidationError 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(CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync))), - IssuerValidationError = new IssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(CustomSecurityTokenException), - new StackFrame("CustomIssuerValidationDelegates.cs", 210), - issuerGuid) - }); - - // IssuerValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerException, inner: CustomSecurityTokenInvalidIssuerException - theoryData.Add(new IssuerExtensibilityTheoryData( - "IssuerValidatorThrows", - issuerGuid, - CustomIssuerValidationDelegates.IssuerValidatorThrows, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidIssuerException), - string.Format(Tokens.LogMessages.IDX10269), - typeof(CustomSecurityTokenInvalidIssuerException)), - IssuerValidationError = new IssuerValidationError( - new MessageDetail( - string.Format(Tokens.LogMessages.IDX10269), null), - ValidationFailureType.IssuerValidatorThrew, - typeof(SecurityTokenInvalidIssuerException), - new StackFrame("JsonWebTokenHandler.ValidateToken.Internal.cs", 300), - issuerGuid, - new SecurityTokenInvalidIssuerException(nameof(CustomIssuerValidationDelegates.IssuerValidatorThrows)) - ) - }); - #endregion - - return theoryData; - } + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_IssuerValidator_Extensibility)); } - public class IssuerExtensibilityTheoryData : ValidateTokenAsyncBaseTheoryData + public static TheoryData GenerateIssuerExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - internal IssuerExtensibilityTheoryData(string testId, string issuer, IssuerValidationDelegateAsync issuerValidator, int extraStackFrames) : base(testId) - { - JsonWebToken = JsonUtilities.CreateUnsignedJsonWebToken("iss", issuer); - ValidationParameters = new ValidationParameters - { - AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, - AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, - IssuerValidatorAsync = issuerValidator, - IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, - LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, - SignatureValidator = SkipValidationDelegates.SkipSignatureValidation, - TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, - TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation - }; - - ExtraStackFrames = extraStackFrames; - } - - public JsonWebToken JsonWebToken { get; } - - public JsonWebTokenHandler JsonWebTokenHandler { get; } = new JsonWebTokenHandler(); - - public bool IsValid { get; set; } - - internal ValidatedIssuer ValidatedIssuer { get; set; } - - internal IssuerValidationError? IssuerValidationError { get; set; } - - internal int ExtraStackFrames { get; } + return ExtensibilityTesting.GenerateIssuerExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "JsonWebTokenHandler.ValidateToken.Internal.cs"); } } } diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs new file mode 100644 index 0000000000..c358e7ad45 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Security.Claims; +using System; +using Microsoft.IdentityModel.Tokens; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests +{ + public class ExtensibilityTheoryData : TheoryDataBase + { + string _tokenHandlerType; + SecurityTokenDescriptor? _securityTokenDescriptor; + + internal ExtensibilityTheoryData( + string testId, + string tokenHandlerType, + int extraStackFrames) : base(testId) + { + ExtraStackFrames = extraStackFrames; + TokenHandler = CreateSecurityTokenHandlerForType(tokenHandlerType); + _tokenHandlerType = tokenHandlerType; + + ValidationParameters = CreateValidationParametersSkippingValidations(); + } + + private IExtensibilityTestingTokenHandler CreateSecurityTokenHandlerForType(string tokenHandlerType) + { + return tokenHandlerType switch + { + "JWT" => new JsonWebTokenHandlerWithResult(), + "SAML" => new SamlSecurityTokenHandlerWithResult(), + "SAML2" => new Saml2SecurityTokenHandlerWithResult(), + _ => throw new NotImplementedException(tokenHandlerType) + }; + } + + private SecurityTokenDescriptor PopulateSubjectForSecurityTokenDescriptor( + SecurityTokenDescriptor securityTokenDescriptor, + string tokenHandlerType) + { + ClaimsIdentity subject = tokenHandlerType switch + { + "JWT" => Default.ClaimsIdentity, + "SAML" or "SAML2" => Default.SamlClaimsIdentity, + _ => throw new NotImplementedException(tokenHandlerType) + }; + + securityTokenDescriptor.Subject = subject; + + return securityTokenDescriptor; + } + + private ValidationParameters CreateValidationParametersSkippingValidations() + { + var validationParameters = new ValidationParameters(); + + validationParameters.AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation; + validationParameters.AudienceValidator = SkipValidationDelegates.SkipAudienceValidation; + validationParameters.IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation; + validationParameters.IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation; + validationParameters.LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation; + validationParameters.SignatureValidator = SkipValidationDelegates.SkipSignatureValidation; + validationParameters.TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation; + validationParameters.TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation; + + return validationParameters; + } + + public SecurityTokenDescriptor SecurityTokenDescriptor + { + get => _securityTokenDescriptor!; + set => _securityTokenDescriptor = PopulateSubjectForSecurityTokenDescriptor(value, _tokenHandlerType); + } + + internal IExtensibilityTestingTokenHandler TokenHandler { get; } + + public bool IsValid { get; set; } + + internal ValidationParameters ValidationParameters { get; } + + internal ValidationError? ValidationError { get; set; } + + internal int ExtraStackFrames { get; } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IExtensibilityTestingTokenHandler.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IExtensibilityTestingTokenHandler.cs new file mode 100644 index 0000000000..62902868cd --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IExtensibilityTestingTokenHandler.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.IdentityModel.JsonWebTokens; +using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Tokens.Saml; +using Microsoft.IdentityModel.Tokens.Saml2; + +namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests +{ + // This interface is used to test the extensibility of the ValidateTokenAsync method + // in the JsonWebTokenHandler, SamlSecurityTokenHandler, and Saml2SecurityTokenHandler classes, + // since the ValidateTokenAsync method with ValidationParameters is not part of any shared interface. + internal interface IExtensibilityTestingTokenHandler + { + Task> ValidateTokenAsync( + SecurityToken token, + ValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken); + + SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor); + } + + // Because the ValidateTokenAsync method in the token handler subclasses is internal, we need + // to create classes that implement the IValidateTokenAsyncResult interface to use in tests. + internal class JsonWebTokenHandlerWithResult : IExtensibilityTestingTokenHandler + { + private readonly JsonWebTokenHandler _handler = new JsonWebTokenHandler(); + + public JsonWebTokenHandlerWithResult() + { + } + + public async Task> ValidateTokenAsync( + SecurityToken token, + ValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken) + { + return await _handler.ValidateTokenAsync(token, validationParameters, callContext, cancellationToken); + } + + public SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) + { + return _handler.ReadToken(_handler.CreateToken(tokenDescriptor)); + } + } + + internal class SamlSecurityTokenHandlerWithResult : IExtensibilityTestingTokenHandler + { + private readonly SamlSecurityTokenHandler _handler = new SamlSecurityTokenHandler(); + + public async Task> ValidateTokenAsync( + SecurityToken token, + ValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken) + { + return await _handler.ValidateTokenAsync(token, validationParameters, callContext, cancellationToken); + } + + public SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) + { + SamlSecurityToken token = (SamlSecurityToken)_handler.CreateToken(tokenDescriptor); + // SamlSecurityTokenHandler.CreateToken does not set correctly the signature on the token. + // Reading the token from the CanonicalString will set the signature correctly. + return _handler.ReadToken(token.Assertion.CanonicalString); + } + } + + internal class Saml2SecurityTokenHandlerWithResult : IExtensibilityTestingTokenHandler + { + private readonly Saml2SecurityTokenHandler _handler = new Saml2SecurityTokenHandler(); + + public async Task> ValidateTokenAsync( + SecurityToken token, + ValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken) + { + return await _handler.ValidateTokenAsync(token, validationParameters, callContext, cancellationToken); + } + + public SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) + { + Saml2SecurityToken token = (Saml2SecurityToken)_handler.CreateToken(tokenDescriptor); + // SamlSecurityTokenHandler.CreateToken does not set correctly the signature on the token. + // Reading the token from the CanonicalString will set the signature correctly. + return _handler.ReadToken(token.Assertion.CanonicalString); + } + } +} diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTestCases.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTestCases.cs new file mode 100644 index 0000000000..9a6a669aa9 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTestCases.cs @@ -0,0 +1,205 @@ +// 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; + +namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests +{ + public partial class ExtensibilityTesting + { + public static TheoryData GenerateIssuerExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames, + string stackFrameFileName) + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + string issuerGuid = Guid.NewGuid().ToString(); + + #region return CustomIssuerValidationError + // Test cases where delegate is overridden and return an CustomIssuerValidationError + // CustomIssuerValidationError : IssuerValidationError, ExceptionType: SecurityTokenInvalidIssuerException + theoryData.Add(new IssuerExtensibilityTheoryData( + "CustomIssuerValidatorDelegate", + tokenHandlerType, + issuerGuid, + CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync, + extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync)), + ValidationError = new CustomIssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(SecurityTokenInvalidIssuerException), + new StackFrame("CustomIssuerValidationDelegates.cs", 0), + issuerGuid) + }); + + // CustomIssuerValidationError : IssuerValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerException : SecurityTokenInvalidIssuerException + theoryData.Add(new IssuerExtensibilityTheoryData( + "CustomIssuerValidatorCustomExceptionDelegate", + tokenHandlerType, + issuerGuid, + CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync, + extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync)), + ValidationError = new CustomIssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(CustomSecurityTokenInvalidIssuerException), + new StackFrame("CustomIssuerValidationDelegates.cs", 0), + issuerGuid), + }); + + // CustomIssuerValidationError : IssuerValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new IssuerExtensibilityTheoryData( + "CustomIssuerValidatorUnknownExceptionDelegate", + tokenHandlerType, + issuerGuid, + CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync, + extraStackFrames) + { + // CustomIssuerValidationError 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(CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync))), + ValidationError = new CustomIssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomIssuerValidationDelegates.cs", 0), + issuerGuid), + }); + + // CustomIssuerValidationError : IssuerValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomIssuerValidationFailureType + theoryData.Add(new IssuerExtensibilityTheoryData( + "CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegate", + tokenHandlerType, + issuerGuid, + CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync, + extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync)), + ValidationError = new CustomIssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync), null), + CustomIssuerValidationError.CustomIssuerValidationFailureType, + typeof(CustomSecurityTokenInvalidIssuerException), + new StackFrame("CustomIssuerValidationDelegates.cs", 0), + issuerGuid, + null), + }); + #endregion + + #region return IssuerValidationError + // Test cases where delegate is overridden and return an IssuerValidationError + // IssuerValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerException + theoryData.Add(new IssuerExtensibilityTheoryData( + "IssuerValidatorDelegate", + tokenHandlerType, + issuerGuid, + CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync, + extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync)), + ValidationError = new IssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(SecurityTokenInvalidIssuerException), + new StackFrame("CustomIssuerValidationDelegates.cs", 0), + issuerGuid) + }); + + // IssuerValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerException : SecurityTokenInvalidIssuerException + theoryData.Add(new IssuerExtensibilityTheoryData( + "IssuerValidatorCustomIssuerExceptionTypeDelegate", + tokenHandlerType, + issuerGuid, + CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync, + extraStackFrames) + { + // IssuerValidationError does not handle the exception type 'CustomSecurityTokenInvalidIssuerException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidIssuerException), + nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync))), + ValidationError = new IssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(CustomSecurityTokenInvalidIssuerException), + new StackFrame("CustomIssuerValidationDelegates.cs", 0), + issuerGuid) + }); + + // IssuerValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new IssuerExtensibilityTheoryData( + "IssuerValidatorCustomExceptionTypeDelegate", + tokenHandlerType, + issuerGuid, + CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync, + extraStackFrames) + { + // IssuerValidationError 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(CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync))), + ValidationError = new IssuerValidationError( + new MessageDetail( + nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync), null), + ValidationFailureType.IssuerValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomIssuerValidationDelegates.cs", 0), + issuerGuid) + }); + + // IssuerValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerException, inner: CustomSecurityTokenInvalidIssuerException + theoryData.Add(new IssuerExtensibilityTheoryData( + "IssuerValidatorThrows", + tokenHandlerType, + issuerGuid, + CustomIssuerValidationDelegates.IssuerValidatorThrows, + extraStackFrames - 1) // when throwing an exception, the stack trace contains one less frame + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidIssuerException), + string.Format(Tokens.LogMessages.IDX10269), + typeof(CustomSecurityTokenInvalidIssuerException)), + ValidationError = new IssuerValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10269), null), + ValidationFailureType.IssuerValidatorThrew, + typeof(SecurityTokenInvalidIssuerException), + new StackFrame(stackFrameFileName, 0), + issuerGuid, + new SecurityTokenInvalidIssuerException(nameof(CustomIssuerValidationDelegates.IssuerValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } +} diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTheoryData.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTheoryData.cs new file mode 100644 index 0000000000..12b4c53c28 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTheoryData.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.IdentityModel.Tokens; + +namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests +{ + public class IssuerExtensibilityTheoryData : ExtensibilityTheoryData + { + internal IssuerExtensibilityTheoryData( + string testId, + string tokenHandlerType, + string issuer, + IssuerValidationDelegateAsync issuerValidationDelegate, + int extraStackFrames) : base(testId, tokenHandlerType, extraStackFrames) + { + SecurityTokenDescriptor = new() + { + Issuer = issuer, + }; + + ValidationParameters.IssuerValidatorAsync = issuerValidationDelegate; + } + } +} diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ValidateTokenAsyncExtensibility.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ValidateTokenAsyncExtensibility.cs new file mode 100644 index 0000000000..f5cc14145e --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ValidateTokenAsyncExtensibility.cs @@ -0,0 +1,50 @@ +// 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.Tokens; + +namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests +{ + public partial class ExtensibilityTesting + { + public static async Task ValidateTokenAsync_Extensibility(ExtensibilityTheoryData theoryData, object testInstance, string methodName) + { + var context = TestUtilities.WriteHeader($"{testInstance}.{methodName}", theoryData); + context.IgnoreType = false; + for (int i = 0; i < theoryData.ExtraStackFrames; i++) + theoryData.ValidationError!.AddStackFrame(new StackFrame(false)); + + SecurityToken securityToken = theoryData.TokenHandler.CreateToken(theoryData.SecurityTokenDescriptor!); + + try + { + ValidationResult validationResult = await theoryData.TokenHandler.ValidateTokenAsync( + securityToken, + theoryData.ValidationParameters!, + theoryData.CallContext, + CancellationToken.None); + + if (validationResult.IsValid) + { + context.AddDiff("validationResult.IsValid == true, expected false"); + } + else + { + ValidationError validationError = validationResult.UnwrapError(); + IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.ValidationError, context); + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + } + } + catch (Exception ex) + { + context.AddDiff($"ValidateTokenAsync threw an unexpected exception: {ex}."); + } + + TestUtilities.AssertFailIfErrors(context); + } + } +} diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Issuer.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Issuer.cs index 4f5afae851..e5ce7bffc0 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Issuer.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Issuer.cs @@ -1,13 +1,8 @@ // 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.Logging; -using Microsoft.IdentityModel.TestUtils; - +using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -15,263 +10,27 @@ namespace Microsoft.IdentityModel.Tokens.Saml2.Extensibility.Tests { public partial class Saml2SecurityTokenHandlerValidateTokenAsyncTests { - [Theory, MemberData(nameof(Issuer_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateTokenAsync_IssuerValidator_Extensibility(IssuerExtensibilityTheoryData theoryData) + [Theory, MemberData( + nameof(GenerateIssuerExtensibilityTestCases), + parameters: ["SAML2", 1], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_IssuerValidator_Extensibility( + IssuerExtensibilityTheoryData theoryData) { - var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_IssuerValidator_Extensibility)}", theoryData); - context.IgnoreType = false; - for (int i = 0; i < theoryData.ExtraStackFrames; i++) - theoryData.IssuerValidationError!.AddStackFrame(new StackFrame(false)); - - try - { - ValidationResult validationResult = await theoryData.SamlSecurityTokenHandler.ValidateTokenAsync( - theoryData.SamlToken!, - theoryData.ValidationParameters!, - theoryData.CallContext, - CancellationToken.None); - - if (validationResult.IsValid) - { - ValidatedToken validatedToken = validationResult.UnwrapResult(); - if (validatedToken.ValidatedIssuer.HasValue) - IdentityComparer.AreValidatedIssuersEqual(validatedToken.ValidatedIssuer.Value, theoryData.ValidatedIssuer, context); - } - else - { - ValidationError validationError = validationResult.UnwrapError(); - IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.IssuerValidationError, context); - theoryData.ExpectedException.ProcessException(validationError.GetException(), context); - } - } - catch (Exception ex) - { - theoryData.ExpectedException.ProcessException(ex, context); - } - - TestUtilities.AssertFailIfErrors(context); + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_IssuerValidator_Extensibility)); } - public static TheoryData Issuer_ExtensibilityTestCases + public static TheoryData GenerateIssuerExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - get - { - var theoryData = new TheoryData(); - CallContext callContext = new CallContext(); - string issuerGuid = Guid.NewGuid().ToString(); - - #region return CustomIssuerValidationError - // Test cases where delegate is overridden and return an CustomIssuerValidationError - // CustomIssuerValidationError : IssuerValidationError, ExceptionType: SecurityTokenInvalidIssuerException - theoryData.Add(new IssuerExtensibilityTheoryData( - "CustomIssuerValidatorDelegate", - issuerGuid, - CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidIssuerException), - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync)), - IssuerValidationError = new CustomIssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(SecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates.cs", 88), - issuerGuid) - }); - - // CustomIssuerValidationError : IssuerValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerException : SecurityTokenInvalidIssuerException - theoryData.Add(new IssuerExtensibilityTheoryData( - "CustomIssuerValidatorCustomExceptionDelegate", - issuerGuid, - CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidIssuerException), - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync)), - IssuerValidationError = new CustomIssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(CustomSecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates.cs", 107), - issuerGuid), - }); - - // CustomIssuerValidationError : IssuerValidationError, ExceptionType: NotSupportedException : SystemException - theoryData.Add(new IssuerExtensibilityTheoryData( - "CustomIssuerValidatorUnknownExceptionDelegate", - issuerGuid, - CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync, - extraStackFrames: 1) - { - // CustomIssuerValidationError 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(CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync))), - IssuerValidationError = new CustomIssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(NotSupportedException), - new StackFrame("CustomIssuerValidationDelegates.cs", 139), - issuerGuid), - }); - - // CustomIssuerValidationError : IssuerValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomIssuerValidationFailureType - theoryData.Add(new IssuerExtensibilityTheoryData( - "CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegate", - issuerGuid, - CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidIssuerException), - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync)), - IssuerValidationError = new CustomIssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync), null), - CustomIssuerValidationError.CustomIssuerValidationFailureType, - typeof(CustomSecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates.cs", 123), - issuerGuid), - }); - #endregion - - #region return IssuerValidationError - // Test cases where delegate is overridden and return an IssuerValidationError - // IssuerValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerException - theoryData.Add(new IssuerExtensibilityTheoryData( - "IssuerValidatorDelegate", - issuerGuid, - CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidIssuerException), - nameof(CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync)), - IssuerValidationError = new IssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(SecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates.cs", 169), - issuerGuid) - }); - - // IssuerValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerException : SecurityTokenInvalidIssuerException - theoryData.Add(new IssuerExtensibilityTheoryData( - "IssuerValidatorCustomIssuerExceptionTypeDelegate", - issuerGuid, - CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync, - extraStackFrames: 1) - { - // IssuerValidationError does not handle the exception type 'CustomSecurityTokenInvalidIssuerException' - ExpectedException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenInvalidIssuerException), - nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync))), - IssuerValidationError = new IssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(CustomSecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates.cs", 196), - issuerGuid) - }); - - // IssuerValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException - theoryData.Add(new IssuerExtensibilityTheoryData( - "IssuerValidatorCustomExceptionTypeDelegate", - issuerGuid, - CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync, - extraStackFrames: 1) - { - // IssuerValidationError 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(CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync))), - IssuerValidationError = new IssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(CustomSecurityTokenException), - new StackFrame("CustomIssuerValidationDelegates.cs", 210), - issuerGuid) - }); - - // IssuerValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerException, inner: CustomSecurityTokenInvalidIssuerException - theoryData.Add(new IssuerExtensibilityTheoryData( - "IssuerValidatorThrows", - issuerGuid, - CustomIssuerValidationDelegates.IssuerValidatorThrows, - extraStackFrames: 0) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidIssuerException), - string.Format(Tokens.LogMessages.IDX10269), - typeof(CustomSecurityTokenInvalidIssuerException)), - IssuerValidationError = new IssuerValidationError( - new MessageDetail( - string.Format(Tokens.LogMessages.IDX10269), null), - ValidationFailureType.IssuerValidatorThrew, - typeof(SecurityTokenInvalidIssuerException), - new StackFrame("Saml2SecurityTokenHandler.ValidateToken.Internal.cs", 95), - issuerGuid, - new SecurityTokenInvalidIssuerException(nameof(CustomIssuerValidationDelegates.IssuerValidatorThrows)) - ) - }); - #endregion - - return theoryData; - } - } - - public class IssuerExtensibilityTheoryData : TheoryDataBase - { - internal IssuerExtensibilityTheoryData(string testId, string issuer, IssuerValidationDelegateAsync issuerValidator, int extraStackFrames) : base(testId) - { - SamlToken = (Saml2SecurityToken)SamlSecurityTokenHandler.CreateToken(new() - { - Subject = Default.SamlClaimsIdentity, - Issuer = issuer, - }); - - ValidationParameters = new ValidationParameters - { - AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, - AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, - IssuerValidatorAsync = issuerValidator, - IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, - LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, - SignatureValidator = SkipValidationDelegates.SkipSignatureValidation, - TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, - TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation - }; - - ExtraStackFrames = extraStackFrames; - } - - public Saml2SecurityToken SamlToken { get; } - - public Saml2SecurityTokenHandler SamlSecurityTokenHandler { get; } = new Saml2SecurityTokenHandler(); - - public bool IsValid { get; set; } - - internal ValidatedIssuer ValidatedIssuer { get; set; } - - internal ValidationParameters? ValidationParameters { get; set; } - - internal IssuerValidationError? IssuerValidationError { get; set; } - - internal int ExtraStackFrames { get; } + return ExtensibilityTesting.GenerateIssuerExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "Saml2SecurityTokenHandler.ValidateToken.Internal.cs"); } } } diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Issuer.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Issuer.cs index af2e6822d9..b0c95b0952 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Issuer.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Issuer.cs @@ -1,13 +1,8 @@ // 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.Logging; -using Microsoft.IdentityModel.TestUtils; - +using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -15,263 +10,27 @@ namespace Microsoft.IdentityModel.Tokens.Saml.Extensibility.Tests { public partial class SamlSecurityTokenHandlerValidateTokenAsyncTests { - [Theory, MemberData(nameof(Issuer_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateTokenAsync_IssuerValidator_Extensibility(IssuerExtensibilityTheoryData theoryData) + [Theory, MemberData( + nameof(GenerateIssuerExtensibilityTestCases), + parameters: ["SAML", 1], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_IssuerValidator_Extensibility( + IssuerExtensibilityTheoryData theoryData) { - var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_IssuerValidator_Extensibility)}", theoryData); - context.IgnoreType = false; - for (int i = 0; i < theoryData.ExtraStackFrames; i++) - theoryData.IssuerValidationError!.AddStackFrame(new StackFrame(false)); - - try - { - ValidationResult validationResult = await theoryData.SamlSecurityTokenHandler.ValidateTokenAsync( - theoryData.SamlToken!, - theoryData.ValidationParameters!, - theoryData.CallContext, - CancellationToken.None); - - if (validationResult.IsValid) - { - ValidatedToken validatedToken = validationResult.UnwrapResult(); - if (validatedToken.ValidatedIssuer.HasValue) - IdentityComparer.AreValidatedIssuersEqual(validatedToken.ValidatedIssuer.Value, theoryData.ValidatedIssuer, context); - } - else - { - ValidationError validationError = validationResult.UnwrapError(); - IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.IssuerValidationError, context); - theoryData.ExpectedException.ProcessException(validationError.GetException(), context); - } - } - catch (Exception ex) - { - theoryData.ExpectedException.ProcessException(ex, context); - } - - TestUtilities.AssertFailIfErrors(context); + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_IssuerValidator_Extensibility)); } - public static TheoryData Issuer_ExtensibilityTestCases + public static TheoryData GenerateIssuerExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - get - { - var theoryData = new TheoryData(); - CallContext callContext = new CallContext(); - string issuerGuid = Guid.NewGuid().ToString(); - - #region return CustomIssuerValidationError - // Test cases where delegate is overridden and return an CustomIssuerValidationError - // CustomIssuerValidationError : IssuerValidationError, ExceptionType: SecurityTokenInvalidIssuerException - theoryData.Add(new IssuerExtensibilityTheoryData( - "CustomIssuerValidatorDelegate", - issuerGuid, - CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidIssuerException), - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync)), - IssuerValidationError = new CustomIssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(SecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates.cs", 88), - issuerGuid) - }); - - // CustomIssuerValidationError : IssuerValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerException : SecurityTokenInvalidIssuerException - theoryData.Add(new IssuerExtensibilityTheoryData( - "CustomIssuerValidatorCustomExceptionDelegate", - issuerGuid, - CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidIssuerException), - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync)), - IssuerValidationError = new CustomIssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(CustomSecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates.cs", 107), - issuerGuid), - }); - - // CustomIssuerValidationError : IssuerValidationError, ExceptionType: NotSupportedException : SystemException - theoryData.Add(new IssuerExtensibilityTheoryData( - "CustomIssuerValidatorUnknownExceptionDelegate", - issuerGuid, - CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync, - extraStackFrames: 1) - { - // CustomIssuerValidationError 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(CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync))), - IssuerValidationError = new CustomIssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorUnknownExceptionDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(NotSupportedException), - new StackFrame("CustomIssuerValidationDelegates.cs", 139), - issuerGuid), - }); - - // CustomIssuerValidationError : IssuerValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomIssuerValidationFailureType - theoryData.Add(new IssuerExtensibilityTheoryData( - "CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegate", - issuerGuid, - CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidIssuerException), - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync)), - IssuerValidationError = new CustomIssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.CustomIssuerValidatorCustomExceptionCustomFailureTypeDelegateAsync), null), - CustomIssuerValidationError.CustomIssuerValidationFailureType, - typeof(CustomSecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates.cs", 123), - issuerGuid), - }); - #endregion - - #region return IssuerValidationError - // Test cases where delegate is overridden and return an IssuerValidationError - // IssuerValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerException - theoryData.Add(new IssuerExtensibilityTheoryData( - "IssuerValidatorDelegate", - issuerGuid, - CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidIssuerException), - nameof(CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync)), - IssuerValidationError = new IssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.IssuerValidatorDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(SecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates.cs", 169), - issuerGuid) - }); - - // IssuerValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerException : SecurityTokenInvalidIssuerException - theoryData.Add(new IssuerExtensibilityTheoryData( - "IssuerValidatorCustomIssuerExceptionTypeDelegate", - issuerGuid, - CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync, - extraStackFrames: 1) - { - // IssuerValidationError does not handle the exception type 'CustomSecurityTokenInvalidIssuerException' - ExpectedException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenInvalidIssuerException), - nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync))), - IssuerValidationError = new IssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomIssuerExceptionTypeDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(CustomSecurityTokenInvalidIssuerException), - new StackFrame("CustomIssuerValidationDelegates.cs", 196), - issuerGuid) - }); - - // IssuerValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException - theoryData.Add(new IssuerExtensibilityTheoryData( - "IssuerValidatorCustomExceptionTypeDelegate", - issuerGuid, - CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync, - extraStackFrames: 1) - { - // IssuerValidationError 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(CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync))), - IssuerValidationError = new IssuerValidationError( - new MessageDetail( - nameof(CustomIssuerValidationDelegates.IssuerValidatorCustomExceptionTypeDelegateAsync), null), - ValidationFailureType.IssuerValidationFailed, - typeof(CustomSecurityTokenException), - new StackFrame("CustomIssuerValidationDelegates.cs", 210), - issuerGuid) - }); - - // IssuerValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerException, inner: CustomSecurityTokenInvalidIssuerException - theoryData.Add(new IssuerExtensibilityTheoryData( - "IssuerValidatorThrows", - issuerGuid, - CustomIssuerValidationDelegates.IssuerValidatorThrows, - extraStackFrames: 0) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidIssuerException), - string.Format(Tokens.LogMessages.IDX10269), - typeof(CustomSecurityTokenInvalidIssuerException)), - IssuerValidationError = new IssuerValidationError( - new MessageDetail( - string.Format(Tokens.LogMessages.IDX10269), null), - ValidationFailureType.IssuerValidatorThrew, - typeof(SecurityTokenInvalidIssuerException), - new StackFrame("SamlSecurityTokenHandler.ValidateToken.Internal.cs", 92), - issuerGuid, - new SecurityTokenInvalidIssuerException(nameof(CustomIssuerValidationDelegates.IssuerValidatorThrows)) - ) - }); - #endregion - - return theoryData; - } - } - - public class IssuerExtensibilityTheoryData : TheoryDataBase - { - internal IssuerExtensibilityTheoryData(string testId, string issuer, IssuerValidationDelegateAsync issuerValidator, int extraStackFrames) : base(testId) - { - SamlToken = (SamlSecurityToken)SamlSecurityTokenHandler.CreateToken(new() - { - Subject = Default.SamlClaimsIdentity, - Issuer = issuer, - }); - - ValidationParameters = new ValidationParameters - { - AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, - AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, - IssuerValidatorAsync = issuerValidator, - IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, - LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, - SignatureValidator = SkipValidationDelegates.SkipSignatureValidation, - TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, - TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation - }; - - ExtraStackFrames = extraStackFrames; - } - - public SamlSecurityToken SamlToken { get; } - - public SamlSecurityTokenHandler SamlSecurityTokenHandler { get; } = new SamlSecurityTokenHandler(); - - public bool IsValid { get; set; } - - internal ValidatedIssuer ValidatedIssuer { get; set; } - - internal ValidationParameters? ValidationParameters { get; set; } - - internal IssuerValidationError? IssuerValidationError { get; set; } - - internal int ExtraStackFrames { get; } + return ExtensibilityTesting.GenerateIssuerExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "SamlSecurityTokenHandler.ValidateToken.Internal.cs"); } } } From 15101143b3ea8c09c113ed491157634d6443607e Mon Sep 17 00:00:00 2001 From: Saurabh Gautam <106561208+SaurabhMSFT@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:07:04 +0000 Subject: [PATCH 09/23] Remove unnecessary aot tests (#3045) --- Wilson.sln | 7 -- .../AotCompatibilityTests.cs | 76 ------------------- .../IgnoreOnAzureDevopsFactAttribute.cs | 28 ------- ...dentityModel.AotCompatibility.Tests.csproj | 17 ----- 4 files changed, 128 deletions(-) delete mode 100644 test/Microsoft.IdentityModel.AotCompatibility.Tests/AotCompatibilityTests.cs delete mode 100644 test/Microsoft.IdentityModel.AotCompatibility.Tests/IgnoreOnAzureDevopsFactAttribute.cs delete mode 100644 test/Microsoft.IdentityModel.AotCompatibility.Tests/Microsoft.IdentityModel.AotCompatibility.Tests.csproj diff --git a/Wilson.sln b/Wilson.sln index 8d3bd81589..5d49f422d7 100644 --- a/Wilson.sln +++ b/Wilson.sln @@ -92,8 +92,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.IdentityModel.Abs EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.IdentityModel.AotCompatibility.TestApp", "test\Microsoft.IdentityModel.AotCompatibility.TestApp\Microsoft.IdentityModel.AotCompatibility.TestApp.csproj", "{8105289F-3D54-4054-9738-5985F3B6CF2C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.IdentityModel.AotCompatibility.Tests", "test\Microsoft.IdentityModel.AotCompatibility.Tests\Microsoft.IdentityModel.AotCompatibility.Tests.csproj", "{CD0EEF56-7221-4420-8181-48EE82E91306}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.IdentityModel.Benchmarks", "benchmark\Microsoft.IdentityModel.Benchmarks\Microsoft.IdentityModel.Benchmarks.csproj", "{F1BB31E4-8865-4425-8BD4-94F1815C16E0}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{BC99A01F-1C6E-4994-8991-4919A9B096E1}" @@ -225,10 +223,6 @@ Global {8105289F-3D54-4054-9738-5985F3B6CF2C}.Debug|Any CPU.Build.0 = Debug|Any CPU {8105289F-3D54-4054-9738-5985F3B6CF2C}.Release|Any CPU.ActiveCfg = Release|Any CPU {8105289F-3D54-4054-9738-5985F3B6CF2C}.Release|Any CPU.Build.0 = Release|Any CPU - {CD0EEF56-7221-4420-8181-48EE82E91306}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CD0EEF56-7221-4420-8181-48EE82E91306}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CD0EEF56-7221-4420-8181-48EE82E91306}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CD0EEF56-7221-4420-8181-48EE82E91306}.Release|Any CPU.Build.0 = Release|Any CPU {F1BB31E4-8865-4425-8BD4-94F1815C16E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F1BB31E4-8865-4425-8BD4-94F1815C16E0}.Debug|Any CPU.Build.0 = Debug|Any CPU {F1BB31E4-8865-4425-8BD4-94F1815C16E0}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -267,7 +261,6 @@ Global {C1F5A997-FAA9-45E5-8D28-D4E92D4A034D} = {EB14B99B-2255-45BC-BF14-E488DCD4A4BA} {EF9A4431-6D2C-4DD1-BF6B-6F2CC619DEE1} = {8905D2E3-4499-4A86-BF3E-F098F228DD59} {8105289F-3D54-4054-9738-5985F3B6CF2C} = {8905D2E3-4499-4A86-BF3E-F098F228DD59} - {CD0EEF56-7221-4420-8181-48EE82E91306} = {8905D2E3-4499-4A86-BF3E-F098F228DD59} {F1BB31E4-8865-4425-8BD4-94F1815C16E0} = {2F79F3C4-F4E3-46DD-8B34-8EF403A6F7F5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/test/Microsoft.IdentityModel.AotCompatibility.Tests/AotCompatibilityTests.cs b/test/Microsoft.IdentityModel.AotCompatibility.Tests/AotCompatibilityTests.cs deleted file mode 100644 index 1c5af61ae1..0000000000 --- a/test/Microsoft.IdentityModel.AotCompatibility.Tests/AotCompatibilityTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System.Diagnostics; -using System.IO; -using Xunit; -using Xunit.Abstractions; - -namespace Microsoft.IdentityModel.AotCompatibility.Tests -{ - public class AotCompatibilityTests - { - private ITestOutputHelper _testOutputHelper; - - public AotCompatibilityTests(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - } - - /// - /// This test ensures that the intended APIs of the Microsoft.IdentityModel libraries are - /// trimming and NativeAOT compatible. - /// - /// This test follows the instructions in https://learn.microsoft.com/dotnet/core/deploying/trimming/prepare-libraries-for-trimming#show-all-warnings-with-sample-application - /// - /// If this test fails, it is due to adding trimming and/or AOT incompatible changes - /// to code that is supposed to be compatible. - /// - /// To diagnose the problem, inspect the test output which will contain the trimming and AOT errors. For example: - /// - /// error IL2091: 'T' generic argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors' - /// - /// You can also 'dotnet publish' the 'Microsoft.IdentityModel.AotCompatibility.TestApp.csproj' as well to get the errors. - /// - [IgnoreOnAzureDevopsFact] - public void EnsureAotCompatibility() - { - string testAppPath = Path.Combine("..", "..", "..", "..", "Microsoft.IdentityModel.AotCompatibility.TestApp"); - string testAppProject = "Microsoft.IdentityModel.AotCompatibility.TestApp.csproj"; - -#if NET9_0_OR_GREATER - string framework = "net9.0"; -#else - string framework = "net8.0"; -#endif - - // ensure we run a clean publish every time - DirectoryInfo testObjDir = new DirectoryInfo(Path.Combine(testAppPath, "obj")); - if (testObjDir.Exists) - { - testObjDir.Delete(recursive: true); - } - - var process = new Process(); - // set '-nodereuse:false /p:UseSharedCompilation=false' so the MSBuild and Roslyn server processes don't hang around, which may hang the test in CI - process.StartInfo = new ProcessStartInfo("dotnet", $"publish {testAppProject} --self-contained --framework {framework} -nodereuse:false /p:UseSharedCompilation=false") - { - RedirectStandardOutput = true, - UseShellExecute = false, - CreateNoWindow = true, - WorkingDirectory = testAppPath - }; - process.OutputDataReceived += (sender, eventArgs) => - { - if (eventArgs.Data is not null) - _testOutputHelper.WriteLine(eventArgs.Data); - }; - process.Start(); - process.BeginOutputReadLine(); - - Assert.True(process.WaitForExit(milliseconds: 180_000), "dotnet publish command timed out after 3 minutes."); - - Assert.True(process.ExitCode == 0, "Publishing the AotCompatibility app failed. See test output for more details."); - } - } -} diff --git a/test/Microsoft.IdentityModel.AotCompatibility.Tests/IgnoreOnAzureDevopsFactAttribute.cs b/test/Microsoft.IdentityModel.AotCompatibility.Tests/IgnoreOnAzureDevopsFactAttribute.cs deleted file mode 100644 index cc3c994837..0000000000 --- a/test/Microsoft.IdentityModel.AotCompatibility.Tests/IgnoreOnAzureDevopsFactAttribute.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using Xunit; - -namespace Microsoft.IdentityModel.AotCompatibility.Tests -{ - public sealed class IgnoreOnAzureDevopsFactAttribute : FactAttribute - { - public IgnoreOnAzureDevopsFactAttribute() - { - if (!IsRunningOnAzureDevOps()) - { - return; - } - - Skip = "Ignored on Azure DevOps"; - } - - /// Determine if runtime is Azure DevOps. - /// True if being executed in Azure DevOps, false otherwise. - public static bool IsRunningOnAzureDevOps() - { - return Environment.GetEnvironmentVariable("SYSTEM_DEFINITIONID") != null; - } - } -} diff --git a/test/Microsoft.IdentityModel.AotCompatibility.Tests/Microsoft.IdentityModel.AotCompatibility.Tests.csproj b/test/Microsoft.IdentityModel.AotCompatibility.Tests/Microsoft.IdentityModel.AotCompatibility.Tests.csproj deleted file mode 100644 index c72cdd01e8..0000000000 --- a/test/Microsoft.IdentityModel.AotCompatibility.Tests/Microsoft.IdentityModel.AotCompatibility.Tests.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - net8.0;net9.0 - $(TargetFrameworks); - 12 - 1.0.0-preview - - - - - - - From da9b16f7b4532e6973d72a1586297c2a19106697 Mon Sep 17 00:00:00 2001 From: Saurabh Gautam <106561208+SaurabhMSFT@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:31:59 +0000 Subject: [PATCH 10/23] Fix powershell script for nuget update (#3046) --- build/template-Build-run-tests-sign.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build/template-Build-run-tests-sign.yml b/build/template-Build-run-tests-sign.yml index 4c79d9baa9..f9e0b1d5e1 100644 --- a/build/template-Build-run-tests-sign.yml +++ b/build/template-Build-run-tests-sign.yml @@ -53,7 +53,10 @@ steps: dotnet nuget add source https://identitydivision.pkgs.visualstudio.com/_packaging/IDDP/nuget/v3/index.json -n IDDP dotnet nuget list source } + workingDirectory: '$(Build.SourcesDirectory)\$(WilsonSourceDirectory)' displayName: 'Remove external "NuGet" Source and add "IDDP artifacts" as a NuGet Source, if needed.' + env: + DOTNET_NOLOGO: 1 - task: DotNetCoreCLI@2 displayName: Build From 131a363bc5ecdf73855111058eff850b31c9cb10 Mon Sep 17 00:00:00 2001 From: Sergei Smelov Date: Tue, 3 Dec 2024 19:51:17 +0100 Subject: [PATCH 11/23] Fix bug with AadIssuerValidator (#3042) * Fix bug with AadIssuerValidator * Update --- .../AadIssuerValidator/AadIssuerValidator.cs | 2 +- .../AadIssuerValidatorTests.cs | 9 ++++++++- .../ValidatorConstants.cs | 2 ++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs index dd95322e06..72771086b5 100644 --- a/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs +++ b/src/Microsoft.IdentityModel.Validators/AadIssuerValidator/AadIssuerValidator.cs @@ -399,7 +399,7 @@ internal static bool IsValidIssuer(string issuerTemplate, string tenantId, strin return false; // Ensure tokenIssuer is atleast as long as issuerTemplate with tenantIdTemplate replaced - if (tokenIssuer.Length <= templateTenantIdPosition + tenantId.Length) + if (tokenIssuer.Length < templateTenantIdPosition + tenantId.Length) return false; // Ensure the tenant ID in the token issuer matches the expected tenant ID diff --git a/test/Microsoft.IdentityModel.Validators.Tests/AadIssuerValidatorTests.cs b/test/Microsoft.IdentityModel.Validators.Tests/AadIssuerValidatorTests.cs index 10d4575a33..0fa85ed1ce 100644 --- a/test/Microsoft.IdentityModel.Validators.Tests/AadIssuerValidatorTests.cs +++ b/test/Microsoft.IdentityModel.Validators.Tests/AadIssuerValidatorTests.cs @@ -26,6 +26,13 @@ public static TheoryData AadIssuerValidationTestCa var theoryData = new TheoryData { // Success cases + new AadIssuerValidatorTheoryData("V1_TemplateWithoutTrailingSlash_Matches_V1_IssuerWithoutTrailingSlash_Success") + { + TemplatedIssuer = ValidatorConstants.AadIssuerV1CommonAuthorityWithoutTrailingSlash, + TokenIssuer = ValidatorConstants.V1IssuerWithoutTrailingSlash, + TenantIdClaim = ValidatorConstants.TenantIdAsGuid, + ExpectedResult = true, + }, new AadIssuerValidatorTheoryData("V1_Template_Matches_V1_Issuer_Success") { TemplatedIssuer = ValidatorConstants.AadIssuerV1CommonAuthority, @@ -106,7 +113,7 @@ public static TheoryData AadIssuerValidationTestCa public class AadIssuerValidatorTheoryData : TheoryDataBase { - public AadIssuerValidatorTheoryData() {} + public AadIssuerValidatorTheoryData() { } public AadIssuerValidatorTheoryData(string testId) : base(testId) { } diff --git a/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs b/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs index 920b6c628f..4cca5d8e88 100644 --- a/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs +++ b/test/Microsoft.IdentityModel.Validators.Tests/ValidatorConstants.cs @@ -40,8 +40,10 @@ internal class ValidatorConstants public const string UsGovIssuer = "https://login.microsoftonline.us/" + UsGovTenantId + "/v2.0"; public const string UsGovTenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47"; public const string V1Issuer = "https://sts.windows.net/f645ad92-e38d-4d1a-b510-d1b09a74a8ca/"; + public const string V1IssuerWithoutTrailingSlash = "https://sts.windows.net/f645ad92-e38d-4d1a-b510-d1b09a74a8ca"; public const string V1IssuerPPE = "https://sts.windows-ppe.net/f645ad92-e38d-4d1a-b510-d1b09a74a8ca/"; public const string AadIssuerV1CommonAuthority = "https://sts.windows.net/{tenantid}/"; + public const string AadIssuerV1CommonAuthorityWithoutTrailingSlash = "https://sts.windows.net/{tenantid}"; public const string AadIssuerV11CommonAuthority = AadInstance + "/{tenantid}/v1.1"; public const string AadIssuerV2CommonAuthority = AadInstance + "/{tenantid}/v2.0"; From 80b17fe0fb2d70b0ca65f56f30454f84fa920cd4 Mon Sep 17 00:00:00 2001 From: jennyf19 Date: Tue, 3 Dec 2024 10:51:50 -0800 Subject: [PATCH 12/23] Update to next version (#3010) --- build/version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/version.props b/build/version.props index 26edcca848..078047415a 100644 --- a/build/version.props +++ b/build/version.props @@ -2,7 +2,7 @@ - 8.2.1 + 8.2.2 preview-$([System.DateTime]::Now.AddYears(-2019).Year)$([System.DateTime]::Now.ToString("MMddHHmmss")) From bdce52977d835d916ad98d55b5b664692e05063b Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Tue, 3 Dec 2024 19:04:31 +0000 Subject: [PATCH 13/23] Remove duplicate code in extensibility tests (#3044) * Updated Algorithm extensibility tests to remove duplicate code * Updated algorithm extensibility test cases. Added missing nullability annotation * Added missing nullability annotations * Updated issuer signing key extensibility tests to remove duplicate code * Updated signature extensibility tests to remove duplicate code * Updated token replay extensibility tests to remove duplicate code * Updated token type extensibility tests to remove duplicate code * Removed duplicated header in files --- ...WebTokenHandler.Extensibility.Algorithm.cs | 333 +---------------- ...nHandler.Extensibility.IssuerSigningKey.cs | 283 +-------------- ...WebTokenHandler.Extensibility.Signature.cs | 265 +------------- ...bTokenHandler.Extensibility.TokenReplay.cs | 276 +------------- ...WebTokenHandler.Extensibility.TokenType.cs | 262 +------------- .../Tests/AlgorithmExtensibilityTestCases.cs | 228 ++++++++++++ .../Tests/AlgorithmExtensibilityTheoryData.cs | 29 ++ .../Tests/ExtensibilityTheoryData.cs | 2 + .../IExtensibilityTestingTokenHandler.cs | 2 + .../Tests/IssuerExtensibilityTestCases.cs | 2 + .../Tests/IssuerExtensibilityTheoryData.cs | 2 + .../IssuerSigningKeyExtensibilityTestCases.cs | 198 +++++++++++ ...IssuerSigningKeyExtensibilityTheoryData.cs | 35 ++ .../Tests/SignatureExtensibilityTestCases.cs | 191 ++++++++++ .../Tests/SignatureExtensibilityTheoryData.cs | 26 ++ .../TokenReplayExtensibilityTestCases.cs | 206 +++++++++++ .../TokenReplayExtensibilityTheoryData.cs | 31 ++ .../Tests/TokenTypeExtensibilityTestCases.cs | 206 +++++++++++ .../Tests/TokenTypeExtensibilityTheoryData.cs | 28 ++ .../Tests/ValidateTokenAsyncExtensibility.cs | 27 +- ...ityTokenHandler.Extensibility.Algorithm.cs | 336 +----------------- ...nHandler.Extensibility.IssuerSigningKey.cs | 282 +-------------- ...ityTokenHandler.Extensibility.Signature.cs | 269 +------------- ...yTokenHandler.Extensibility.TokenReplay.cs | 278 +-------------- ...ityTokenHandler.Extensibility.Algorithm.cs | 336 +----------------- ...nHandler.Extensibility.IssuerSigningKey.cs | 282 +-------------- ...ityTokenHandler.Extensibility.Signature.cs | 269 +------------- ...yTokenHandler.Extensibility.TokenReplay.cs | 278 +-------------- 28 files changed, 1446 insertions(+), 3516 deletions(-) create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AlgorithmExtensibilityTestCases.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AlgorithmExtensibilityTheoryData.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerSigningKeyExtensibilityTestCases.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerSigningKeyExtensibilityTheoryData.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/SignatureExtensibilityTestCases.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/SignatureExtensibilityTheoryData.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenReplayExtensibilityTestCases.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenReplayExtensibilityTheoryData.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenTypeExtensibilityTestCases.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenTypeExtensibilityTheoryData.cs diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Algorithm.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Algorithm.cs index fdfc48ed4a..7de840b3c3 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Algorithm.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Algorithm.cs @@ -1,14 +1,8 @@ // 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.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -16,318 +10,27 @@ namespace Microsoft.IdentityModel.JsonWebTokens.Extensibility.Tests { public partial class JsonWebTokenHandlerValidateTokenAsyncTests { - [Theory, MemberData(nameof(Algorithm_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateTokenAsync_AlgorithmValidator_Extensibility(AlgorithmExtensibilityTheoryData theoryData) + [Theory, MemberData( + nameof(GenerateAlgorithmExtensibilityTestCases), + parameters: ["JWT", 1], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_AlgorithmValidator_Extensibility( + AlgorithmExtensibilityTheoryData theoryData) { - var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_AlgorithmValidator_Extensibility)}", theoryData); - context.IgnoreType = false; - for (int i = 0; i < theoryData.ExtraStackFrames; i++) - theoryData.ValidationError!.AddStackFrame(new StackFrame(false)); - - theoryData.ValidationParameters!.IssuerSigningKeys.Add(theoryData.SigningKey); - - try - { - ValidationResult validationResult = await theoryData.JsonWebTokenHandler.ValidateTokenAsync( - theoryData.JsonWebToken!, - theoryData.ValidationParameters!, - theoryData.CallContext, - CancellationToken.None); - - if (validationResult.IsValid) - { - // We expect the validation to fail, but it passed - context.AddDiff("ValidationResult is Valid."); - } - else - { - ValidationError validationError = validationResult.UnwrapError(); - - if (validationError is SignatureValidationError signatureValidationError && - signatureValidationError.InnerValidationError is not null) - { - IdentityComparer.AreValidationErrorsEqual( - signatureValidationError.InnerValidationError, - theoryData.ValidationError, - context); - } - else - { - IdentityComparer.AreValidationErrorsEqual( - validationError, - theoryData.ValidationError, - context); - } - - var exception = validationError.GetException(); - theoryData.ExpectedException.ProcessException(exception, context); - // Process the inner exception since invalid algorithm exceptions are wrapped inside - // invalid signature exceptions - if (theoryData.ExpectedInnerException is not null) - theoryData.ExpectedInnerException.ProcessException(exception.InnerException, context); - } - } - catch (Exception ex) - { - // We expect the validation to fail, but it threw an exception - context.AddDiff($"ValidateTokenAsync threw exception: {ex}"); - } - - TestUtilities.AssertFailIfErrors(context); + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_AlgorithmValidator_Extensibility)); } - public static TheoryData Algorithm_ExtensibilityTestCases + public static TheoryData GenerateAlgorithmExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - get - { - var theoryData = new TheoryData(); - CallContext callContext = new CallContext(); - var utcNow = DateTime.UtcNow; - var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); - - #region return CustomAlgorithmValidationError - // Test cases where delegate is overridden and return a CustomAlgorithmValidationError - // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "CustomAlgorithmValidatorDelegate", - utcNow, - CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(SecurityTokenInvalidAlgorithmException)), - ExpectedInnerException = new ExpectedException( - typeof(SecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate)), - ValidationError = new CustomAlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(SecurityTokenInvalidAlgorithmException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 160), - "algorithm") - }); - - // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "CustomAlgorithmValidatorCustomExceptionDelegate", - utcNow, - CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(CustomSecurityTokenInvalidAlgorithmException)), - ExpectedInnerException = new ExpectedException( - typeof(CustomSecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate)), - ValidationError = new CustomAlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(CustomSecurityTokenInvalidAlgorithmException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 175), - "algorithm"), - }); - - // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "CustomAlgorithmValidatorUnknownExceptionDelegate", - utcNow, - CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate, - extraStackFrames: 1) - { - // CustomAlgorithmValidationError does not handle the exception type 'NotSupportedException' - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(SecurityTokenException)), - ExpectedInnerException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(NotSupportedException), - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate))), - ValidationError = new CustomAlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(NotSupportedException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 205), - "algorithm"), - }); - - // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate", - utcNow, - CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(CustomSecurityTokenInvalidAlgorithmException)), - ExpectedInnerException = new ExpectedException( - typeof(CustomSecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate)), - ValidationError = new CustomAlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate), null), - CustomAlgorithmValidationError.CustomAlgorithmValidationFailureType, - typeof(CustomSecurityTokenInvalidAlgorithmException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 190), - "algorithm"), - }); - #endregion - - #region return AlgorithmValidationError - // Test cases where delegate is overridden and return an AlgorithmValidationError - // AlgorithmValidationError : ValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "AlgorithmValidatorDelegate", - utcNow, - CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(SecurityTokenInvalidAlgorithmException)), - ExpectedInnerException = new ExpectedException( - typeof(SecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate)), - ValidationError = new AlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(SecurityTokenInvalidAlgorithmException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 235), - "algorithm") - }); - - // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate", - utcNow, - CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate, - extraStackFrames: 1) - { - // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenInvalidAlgorithmException' - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(SecurityTokenException)), - ExpectedInnerException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate))), - ValidationError = new AlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(CustomSecurityTokenInvalidAlgorithmException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 259), - "algorithm") - }); - - // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "AlgorithmValidatorCustomExceptionTypeDelegate", - utcNow, - CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate, - extraStackFrames: 1) - { - // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenException' - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(SecurityTokenException)), - ExpectedInnerException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenException), - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate))), - ValidationError = new AlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(CustomSecurityTokenException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 274), - "algorithm") - }); - - // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidAlgorithmException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "AlgorithmValidatorThrows", - utcNow, - CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10273:", - typeof(CustomSecurityTokenInvalidAlgorithmException)), - ExpectedInnerException = new ExpectedException( - typeof(CustomSecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows)), - ValidationError = new SignatureValidationError( - new MessageDetail( - string.Format(Tokens.LogMessages.IDX10273), null), - ValidationFailureType.AlgorithmValidatorThrew, - typeof(SecurityTokenInvalidSignatureException), - new StackFrame("JsonWebTokenHandler.ValidateSignature.cs", 250), - null, // no inner validation error - new CustomSecurityTokenInvalidAlgorithmException(nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows), null) - ) - }); - #endregion - - return theoryData; - } - } - - public class AlgorithmExtensibilityTheoryData : ValidateTokenAsyncBaseTheoryData - { - internal AlgorithmExtensibilityTheoryData(string testId, DateTime utcNow, AlgorithmValidationDelegate algorithmValidator, int extraStackFrames) : base(testId) - { - // The token is never read by the custom delegtes, so we create a dummy token - JsonWebToken = JsonWebTokenHandler.ReadJsonWebToken(JsonWebTokenHandler.CreateToken(new SecurityTokenDescriptor() - { - SigningCredentials = Default.SymmetricSigningCredentials, - })); - - ValidationParameters = new ValidationParameters - { - // We leave the default signature validator to call the custom algorithm validator - AlgorithmValidator = algorithmValidator, - AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, - IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, - IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, - LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, - TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, - TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation, - }; - - ExtraStackFrames = extraStackFrames; - } - - public JsonWebToken JsonWebToken { get; } - - public JsonWebTokenHandler JsonWebTokenHandler { get; } = new JsonWebTokenHandler(); - - public bool IsValid { get; set; } - - internal ValidationError? ValidationError { get; set; } - - public ExpectedException? ExpectedInnerException { get; set; } - - internal int ExtraStackFrames { get; } - - public SecurityKey SigningKey { get; set; } = Default.SymmetricSigningKey; + return ExtensibilityTesting.GenerateAlgorithmExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "JsonWebTokenHandler.ValidateSignature.cs"); } } } diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.IssuerSigningKey.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.IssuerSigningKey.cs index 3b2bd111c6..fb24e4a51f 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.IssuerSigningKey.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.IssuerSigningKey.cs @@ -1,14 +1,8 @@ // 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.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -16,268 +10,27 @@ namespace Microsoft.IdentityModel.JsonWebTokens.Extensibility.Tests { public partial class JsonWebTokenHandlerValidateTokenAsyncTests { - [Theory, MemberData(nameof(IssuerSigningKey_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility(IssuerSigningKeyExtensibilityTheoryData theoryData) + [Theory, MemberData( + nameof(GenerateIssuerSigningKeyExtensibilityTestCases), + parameters: ["JWT", 2], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility( + IssuerSigningKeyExtensibilityTheoryData theoryData) { - var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility)}", theoryData); - context.IgnoreType = false; - for (int i = 0; i < theoryData.ExtraStackFrames; i++) - theoryData.IssuerSigningKeyValidationError!.AddStackFrame(new StackFrame(false)); - - try - { - ValidationResult validationResult = await theoryData.JsonWebTokenHandler.ValidateTokenAsync( - theoryData.JsonWebToken!, - theoryData.ValidationParameters!, - theoryData.CallContext, - CancellationToken.None); - - if (validationResult.IsValid) - { - context.AddDiff("validationResult.IsValid is true, expected false"); - } - else - { - ValidationError validationError = validationResult.UnwrapError(); - IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.IssuerSigningKeyValidationError, context); - theoryData.ExpectedException.ProcessException(validationError.GetException(), context); - } - } - catch (Exception ex) - { - theoryData.ExpectedException.ProcessException(ex, context); - } - - TestUtilities.AssertFailIfErrors(context); - } - - public static TheoryData IssuerSigningKey_ExtensibilityTestCases - { - get - { - var theoryData = new TheoryData(); - CallContext callContext = new CallContext(); - var utcNow = DateTime.UtcNow; - var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); - - #region return CustomIssuerSigningKeyValidationError - // Test cases where delegate is overridden and return a CustomIssuerSigningKeyValidationError - // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "CustomIssuerSigningKeyValidatorDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSigningKeyException), - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate)), - IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(SecurityTokenInvalidSigningKeyException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 160), - null) - }); - - // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerSigningKeyException : SecurityTokenInvalidIssuerSigningKeyException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "CustomIssuerSigningKeyValidatorCustomExceptionDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidSigningKeyException), - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate)), - IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(CustomSecurityTokenInvalidSigningKeyException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 175), - null), - }); - - // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: NotSupportedException : SystemException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "CustomIssuerSigningKeyValidatorUnknownExceptionDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate, - extraStackFrames: 2) - { - // CustomIssuerSigningKeyValidationError 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(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate))), - IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(NotSupportedException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 205), - null), - }); - - // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidSigningKeyException), - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate)), - IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate), null), - CustomIssuerSigningKeyValidationError.CustomIssuerSigningKeyValidationFailureType, - typeof(CustomSecurityTokenInvalidSigningKeyException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 190), - null), - }); - #endregion - - #region return IssuerSigningKeyValidationError - // Test cases where delegate is overridden and return an IssuerSigningKeyValidationError - // IssuerSigningKeyValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "IssuerSigningKeyValidatorDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSigningKeyException), - nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate)), - IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(SecurityTokenInvalidSigningKeyException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 235), - null) - }); - - // IssuerSigningKeyValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerSigningKeyException : SecurityTokenInvalidIssuerSigningKeyException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate, - extraStackFrames: 2) - { - // IssuerSigningKeyValidationError does not handle the exception type 'CustomSecurityTokenInvalidIssuerSigningKeyException' - ExpectedException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenInvalidSigningKeyException), - nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate))), - IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(CustomSecurityTokenInvalidSigningKeyException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 259), - null) - }); - - // IssuerSigningKeyValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "IssuerSigningKeyValidatorCustomExceptionTypeDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate, - extraStackFrames: 2) - { - // IssuerSigningKeyValidationError 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(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate))), - IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(CustomSecurityTokenException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 274), - null) - }); - - // IssuerSigningKeyValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException, inner: CustomSecurityTokenInvalidIssuerSigningKeyException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "IssuerSigningKeyValidatorThrows", - utcNow, - CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorThrows, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSigningKeyException), - string.Format(Tokens.LogMessages.IDX10274), - typeof(CustomSecurityTokenInvalidSigningKeyException)), - IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( - new MessageDetail( - string.Format(Tokens.LogMessages.IDX10274), null), - ValidationFailureType.IssuerSigningKeyValidatorThrew, - typeof(SecurityTokenInvalidSigningKeyException), - new StackFrame("JsonWebTokenHandler.ValidateToken.Internal.cs", 250), - null, - new SecurityTokenInvalidSigningKeyException(nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorThrows)) - ) - }); - #endregion - - return theoryData; - } + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility)); } - public class IssuerSigningKeyExtensibilityTheoryData : ValidateTokenAsyncBaseTheoryData + public static TheoryData GenerateIssuerSigningKeyExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - internal IssuerSigningKeyExtensibilityTheoryData(string testId, DateTime utcNow, IssuerSigningKeyValidationDelegate issuerSigningKeyValidator, int extraStackFrames) : base(testId) - { - JsonWebToken = JsonWebTokenHandler.ReadJsonWebToken( - JsonWebTokenHandler.CreateToken( - new SecurityTokenDescriptor() - { - IssuedAt = utcNow, - NotBefore = utcNow, - Expires = utcNow + TimeSpan.FromHours(1), - })); - - ValidationParameters = new ValidationParameters - { - AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, - AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, - IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, - IssuerSigningKeyValidator = issuerSigningKeyValidator, - LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, - SignatureValidator = (SecurityToken token, ValidationParameters validationParameters, BaseConfiguration? configuration, CallContext callContext) => - { - token.SigningKey = SigningKey; - - return SigningKey; - }, - TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, - TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation - }; - - ExtraStackFrames = extraStackFrames; - } - - public JsonWebToken JsonWebToken { get; } - - public JsonWebTokenHandler JsonWebTokenHandler { get; } = new JsonWebTokenHandler(); - - public bool IsValid { get; set; } - - public SecurityKey SigningKey { get; set; } = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key; - - internal IssuerSigningKeyValidationError? IssuerSigningKeyValidationError { get; set; } - - internal int ExtraStackFrames { get; } + return ExtensibilityTesting.GenerateIssuerSigningKeyExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "JsonWebTokenHandler.ValidateToken.Internal.cs"); } } } diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Signature.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Signature.cs index ac497daa72..8c6c8a75d0 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Signature.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Signature.cs @@ -1,15 +1,8 @@ // 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 Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -17,249 +10,27 @@ namespace Microsoft.IdentityModel.JsonWebTokens.Extensibility.Tests { public partial class JsonWebTokenHandlerValidateTokenAsyncTests { - [Theory, MemberData(nameof(Signature_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateTokenAsync_SignatureValidator_Extensibility(SignatureExtensibilityTheoryData theoryData) + [Theory, MemberData( + nameof(GenerateSignatureExtensibilityTestCases), + parameters: ["JWT", 3], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_SignatureValidator_Extensibility( + SignatureExtensibilityTheoryData theoryData) { - var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_SignatureValidator_Extensibility)}", theoryData); - context.IgnoreType = false; - for (int i = 0; i < theoryData.ExtraStackFrames; i++) - theoryData.SignatureValidationError!.AddStackFrame(new StackFrame(false)); - - try - { - ValidationResult validationResult = await theoryData.JsonWebTokenHandler.ValidateTokenAsync( - theoryData.JsonWebToken!, - theoryData.ValidationParameters!, - theoryData.CallContext, - CancellationToken.None); - - if (validationResult.IsValid) - { - // We expect the validation to fail, but it passed - context.AddDiff("ValidationResult is Valid."); - } - else - { - ValidationError validationError = validationResult.UnwrapError(); - IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.SignatureValidationError, context); - theoryData.ExpectedException.ProcessException(validationError.GetException(), context); - } - } - catch (Exception ex) - { - theoryData.ExpectedException.ProcessException(ex, context); - } - - TestUtilities.AssertFailIfErrors(context); - } - - public static TheoryData Signature_ExtensibilityTestCases - { - get - { - var theoryData = new TheoryData(); - CallContext callContext = new CallContext(); - var utcNow = DateTime.UtcNow; - var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); - - #region return CustomSignatureValidationError - // Test cases where delegate is overridden and return a CustomSignatureValidationError - // CustomSignatureValidationError : SignatureValidationError, ExceptionType: SecurityTokenInvalidSignatureException - theoryData.Add(new SignatureExtensibilityTheoryData( - "CustomSignatureValidatorDelegate", - utcNow, - CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate, - extraStackFrames: 3) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate)), - SignatureValidationError = new CustomSignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(SecurityTokenInvalidSignatureException), - new StackFrame("CustomSignatureValidationDelegates.cs", 160)) - }); - - // CustomSignatureValidationError : SignatureValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException - theoryData.Add(new SignatureExtensibilityTheoryData( - "CustomSignatureValidatorCustomExceptionDelegate", - utcNow, - CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate, - extraStackFrames: 3) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidSignatureException), - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate)), - SignatureValidationError = new CustomSignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(CustomSecurityTokenInvalidSignatureException), - new StackFrame("CustomSignatureValidationDelegates.cs", 175)), - }); - - // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException - theoryData.Add(new SignatureExtensibilityTheoryData( - "CustomSignatureValidatorUnknownExceptionDelegate", - utcNow, - CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate, - extraStackFrames: 3) - { - // CustomSignatureValidationError 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(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate))), - SignatureValidationError = new CustomSignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(NotSupportedException), - new StackFrame("CustomSignatureValidationDelegates.cs", 205)), - }); - - // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType - theoryData.Add(new SignatureExtensibilityTheoryData( - "CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate", - utcNow, - CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate, - extraStackFrames: 3) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidSignatureException), - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate)), - SignatureValidationError = new CustomSignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate), null), - CustomSignatureValidationError.CustomSignatureValidationFailureType, - typeof(CustomSecurityTokenInvalidSignatureException), - new StackFrame("CustomSignatureValidationDelegates.cs", 190)), - }); - #endregion - - #region return SignatureValidationError - // Test cases where delegate is overridden and return an SignatureValidationError - // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException - theoryData.Add(new SignatureExtensibilityTheoryData( - "SignatureValidatorDelegate", - utcNow, - CustomSignatureValidationDelegates.SignatureValidatorDelegate, - extraStackFrames: 3) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate)), - SignatureValidationError = new SignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(SecurityTokenInvalidSignatureException), - new StackFrame("CustomSignatureValidationDelegates.cs", 235)) - }); - - // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException - theoryData.Add(new SignatureExtensibilityTheoryData( - "SignatureValidatorCustomSignatureExceptionTypeDelegate", - utcNow, - CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate, - extraStackFrames: 3) - { - // SignatureValidationError does not handle the exception type 'CustomSecurityTokenInvalidSignatureException' - ExpectedException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenInvalidSignatureException), - nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate))), - SignatureValidationError = new SignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(CustomSecurityTokenInvalidSignatureException), - new StackFrame("CustomSignatureValidationDelegates.cs", 259)) - }); - - // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException - theoryData.Add(new SignatureExtensibilityTheoryData( - "SignatureValidatorCustomExceptionTypeDelegate", - utcNow, - CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate, - extraStackFrames: 3) - { - // SignatureValidationError 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(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate))), - SignatureValidationError = new SignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(CustomSecurityTokenException), - new StackFrame("CustomSignatureValidationDelegates.cs", 274)) - }); - - // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidSignatureException - theoryData.Add(new SignatureExtensibilityTheoryData( - "SignatureValidatorThrows", - utcNow, - CustomSignatureValidationDelegates.SignatureValidatorThrows, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - string.Format(Tokens.LogMessages.IDX10272), - typeof(CustomSecurityTokenInvalidSignatureException)), - SignatureValidationError = new SignatureValidationError( - new MessageDetail( - string.Format(Tokens.LogMessages.IDX10272), null), - ValidationFailureType.SignatureValidatorThrew, - typeof(SecurityTokenInvalidSignatureException), - new StackFrame("JsonWebTokenHandler.ValidateSignature.cs", 250), - null, // no inner validation error - new SecurityTokenInvalidSignatureException(nameof(CustomSignatureValidationDelegates.SignatureValidatorThrows)) - ) - }); - #endregion - - return theoryData; - } + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_SignatureValidator_Extensibility)); } - public class SignatureExtensibilityTheoryData : ValidateTokenAsyncBaseTheoryData + public static TheoryData GenerateSignatureExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - internal SignatureExtensibilityTheoryData(string testId, DateTime utcNow, SignatureValidationDelegate signatureValidator, int extraStackFrames) : base(testId) - { - // The token is never read by the custom delegtes, so we create a dummy token - JsonWebToken = JsonUtilities.CreateUnsignedJsonWebToken("iss", "issuer"); - - ValidationParameters = new ValidationParameters - { - AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, - AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, - IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, - IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, - LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, - SignatureValidator = signatureValidator, - TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, - TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation - }; - - ExtraStackFrames = extraStackFrames; - } - - public JsonWebToken JsonWebToken { get; } - - public JsonWebTokenHandler JsonWebTokenHandler { get; } = new JsonWebTokenHandler(); - - public bool IsValid { get; set; } - - internal SignatureValidationError? SignatureValidationError { get; set; } - - internal int ExtraStackFrames { get; } + return ExtensibilityTesting.GenerateSignatureExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "JsonWebTokenHandler.ValidateSignature.cs"); } } } diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenReplay.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenReplay.cs index 3f96081d85..7e17dd4ca8 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenReplay.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenReplay.cs @@ -1,14 +1,8 @@ // 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.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -16,261 +10,27 @@ namespace Microsoft.IdentityModel.JsonWebTokens.Extensibility.Tests { public partial class JsonWebTokenHandlerValidateTokenAsyncTests { - [Theory, MemberData(nameof(TokenReplay_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateTokenAsync_TokenReplayValidator_Extensibility(TokenReplayExtensibilityTheoryData theoryData) + [Theory, MemberData( + nameof(GenerateTokenReplayExtensibilityTestCases), + parameters: ["JWT", 2], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_TokenReplayValidator_Extensibility( + TokenReplayExtensibilityTheoryData theoryData) { - var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_TokenReplayValidator_Extensibility)}", theoryData); - context.IgnoreType = false; - for (int i = 0; i < theoryData.ExtraStackFrames; i++) - theoryData.TokenReplayValidationError!.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.TokenReplayValidationError, context); - theoryData.ExpectedException.ProcessException(validationError.GetException(), context); - } - } - catch (Exception ex) - { - theoryData.ExpectedException.ProcessException(ex, context); - } - - TestUtilities.AssertFailIfErrors(context); - } - - public static TheoryData TokenReplay_ExtensibilityTestCases - { - get - { - var theoryData = new TheoryData(); - CallContext callContext = new CallContext(); - var utcNow = DateTime.UtcNow; - var expirationTime = utcNow + TimeSpan.FromHours(1); - - #region return CustomTokenReplayValidationError - // Test cases where delegate is overridden and return a CustomTokenReplayValidationError - // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: SecurityTokenReplayDetectedException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "CustomTokenReplayValidationDelegate", - utcNow, - CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenReplayDetectedException), - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate)), - TokenReplayValidationError = new CustomTokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(SecurityTokenReplayDetectedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 160), - expirationTime) - }); - - // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: CustomSecurityTokenReplayDetectedException : SecurityTokenReplayDetectedException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "CustomTokenReplayValidatorCustomExceptionDelegate", - utcNow, - CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenReplayDetectedException), - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate)), - TokenReplayValidationError = new CustomTokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(CustomSecurityTokenReplayDetectedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 175), - expirationTime), - }); - - // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: NotSupportedException : SystemException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "CustomTokenReplayValidatorUnknownExceptionDelegate", - utcNow, - CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate, - extraStackFrames: 2) - { - // CustomTokenReplayValidationError 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(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate))), - TokenReplayValidationError = new CustomTokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(NotSupportedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 205), - expirationTime), - }); - - // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate", - utcNow, - CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenReplayDetectedException), - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate)), - TokenReplayValidationError = new CustomTokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate), null), - CustomTokenReplayValidationError.CustomTokenReplayValidationFailureType, - typeof(CustomSecurityTokenReplayDetectedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 190), - expirationTime), - }); - #endregion - - #region return TokenReplayValidationError - // Test cases where delegate is overridden and return an TokenReplayValidationError - // TokenReplayValidationError : ValidationError, ExceptionType: SecurityTokenReplayDetectedException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "TokenReplayValidationDelegate", - utcNow, - CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenReplayDetectedException), - nameof(CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate)), - TokenReplayValidationError = new TokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(SecurityTokenReplayDetectedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 235), - expirationTime) - }); - - // TokenReplayValidationError : ValidationError, ExceptionType: CustomSecurityTokenReplayDetectedException : SecurityTokenReplayDetectedException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate", - utcNow, - CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate, - extraStackFrames: 2) - { - // TokenReplayValidationError does not handle the exception type 'CustomSecurityTokenReplayDetectedException' - ExpectedException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenReplayDetectedException), - nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate))), - TokenReplayValidationError = new TokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(CustomSecurityTokenReplayDetectedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 259), - expirationTime) - }); - - // TokenReplayValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "TokenReplayValidatorCustomExceptionTypeDelegate", - utcNow, - CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate, - extraStackFrames: 2) - { - // TokenReplayValidationError 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(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate))), - TokenReplayValidationError = new TokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(CustomSecurityTokenException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 274), - expirationTime) - }); - - // TokenReplayValidationError : ValidationError, ExceptionType: SecurityTokenReplayDetectedException, inner: CustomSecurityTokenReplayDetectedException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "TokenReplayValidatorThrows", - utcNow, - CustomTokenReplayValidationDelegates.TokenReplayValidatorThrows, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenReplayDetectedException), - string.Format(Tokens.LogMessages.IDX10276), - typeof(CustomSecurityTokenReplayDetectedException)), - TokenReplayValidationError = new TokenReplayValidationError( - new MessageDetail( - string.Format(Tokens.LogMessages.IDX10276), null), - ValidationFailureType.TokenReplayValidatorThrew, - typeof(SecurityTokenReplayDetectedException), - new StackFrame("JsonWebTokenHandler.ValidateToken.Internal.cs", 250), - expirationTime, - new SecurityTokenReplayDetectedException(nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorThrows)) - ) - }); - #endregion - - return theoryData; - } + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_TokenReplayValidator_Extensibility)); } - public class TokenReplayExtensibilityTheoryData : ValidateTokenAsyncBaseTheoryData + public static TheoryData GenerateTokenReplayExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - internal TokenReplayExtensibilityTheoryData(string testId, DateTime utcNow, TokenReplayValidationDelegate tokenReplayValidator, int extraStackFrames) : base(testId) - { - JsonWebToken = JsonWebTokenHandler.ReadJsonWebToken( - JsonWebTokenHandler.CreateToken( - new SecurityTokenDescriptor() - { - IssuedAt = utcNow, - NotBefore = utcNow, - Expires = utcNow + TimeSpan.FromHours(1), - })); - - ValidationParameters = new ValidationParameters - { - AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, - AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, - IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, - IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, - LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, - SignatureValidator = SkipValidationDelegates.SkipSignatureValidation, - TokenReplayValidator = tokenReplayValidator, - TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation - }; - - ExtraStackFrames = extraStackFrames; - } - - public JsonWebToken JsonWebToken { get; } - - public JsonWebTokenHandler JsonWebTokenHandler { get; } = new JsonWebTokenHandler(); - - public bool IsValid { get; set; } - - internal TokenReplayValidationError? TokenReplayValidationError { get; set; } - - internal int ExtraStackFrames { get; } + return ExtensibilityTesting.GenerateTokenReplayExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "JsonWebTokenHandler.ValidateToken.Internal.cs"); } } } diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenType.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenType.cs index 08bcd74b40..ecc7f63439 100644 --- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenType.cs +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.TokenType.cs @@ -1,15 +1,8 @@ // 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 Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -17,246 +10,27 @@ 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) + [Theory, MemberData( + nameof(GenerateTokenTypeExtensibilityTestCases), + parameters: ["JWT", 2], + 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); + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_TokenTypeValidator_Extensibility)); } - public static TheoryData TokenType_ExtensibilityTestCases + public static TheoryData GenerateTokenTypeExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - 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; } + return ExtensibilityTesting.GenerateTokenTypeExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "JsonWebTokenHandler.ValidateToken.Internal.cs"); } } } diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AlgorithmExtensibilityTestCases.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AlgorithmExtensibilityTestCases.cs new file mode 100644 index 0000000000..d28fefce62 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AlgorithmExtensibilityTestCases.cs @@ -0,0 +1,228 @@ +// 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 GenerateAlgorithmExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames, + string stackFrameFileName) + { + TheoryData theoryData = new(); + CallContext callContext = new CallContext(); + + #region return CustomAlgorithmValidationError + // Test cases where delegate is overridden and return a CustomAlgorithmValidationError + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorDelegate", + tokenHandlerType, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(SecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 0), + "algorithm") + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorCustomExceptionDelegate", + tokenHandlerType, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 0), + "algorithm"), + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorUnknownExceptionDelegate", + tokenHandlerType, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate, + extraStackFrames: extraStackFrames) + { + // CustomAlgorithmValidationError does not handle the exception type 'NotSupportedException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(NotSupportedException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate))), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 0), + "algorithm"), + }); + + // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate", + tokenHandlerType, + CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate)), + ValidationError = new CustomAlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomAlgorithmValidationError.CustomAlgorithmValidationFailureType, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 0), + "algorithm"), + }); + #endregion + + #region return AlgorithmValidationError + // Test cases where delegate is overridden and return an AlgorithmValidationError + // AlgorithmValidationError : ValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorDelegate", + tokenHandlerType, + CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(SecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate)), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(SecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 0), + "algorithm") + }); + + // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate", + tokenHandlerType, + CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate, + extraStackFrames: extraStackFrames) + { + // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenInvalidAlgorithmException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate))), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenInvalidAlgorithmException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 0), + "algorithm") + }); + + // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorCustomExceptionTypeDelegate", + tokenHandlerType, + CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate, + extraStackFrames: extraStackFrames) + { + // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenException' + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10518:", + typeof(SecurityTokenException)), + ExpectedInnerException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate))), + ValidationError = new AlgorithmValidationError( + new MessageDetail( + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.AlgorithmValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomAlgorithmValidationDelegates.cs", 0), + "algorithm") + }); + + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidAlgorithmException + theoryData.Add(new AlgorithmExtensibilityTheoryData( + "AlgorithmValidatorThrows", + tokenHandlerType, + CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows, + extraStackFrames: extraStackFrames + 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + "IDX10273:", + typeof(CustomSecurityTokenInvalidAlgorithmException)), + ExpectedInnerException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAlgorithmException), + nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows)), + ValidationError = new SignatureValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10273), null), + ValidationFailureType.AlgorithmValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame(stackFrameFileName, 0), + null, // no inner validation error + new CustomSecurityTokenInvalidAlgorithmException(nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows), null) + ) + }); + #endregion + + return theoryData; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AlgorithmExtensibilityTheoryData.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AlgorithmExtensibilityTheoryData.cs new file mode 100644 index 0000000000..90a6888d32 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AlgorithmExtensibilityTheoryData.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.IdentityModel.Tokens; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests +{ + public class AlgorithmExtensibilityTheoryData : ExtensibilityTheoryData + { + internal AlgorithmExtensibilityTheoryData( + string testId, + string tokenHandlerType, + AlgorithmValidationDelegate algorithmValidationDelegate, + int extraStackFrames) : base(testId, tokenHandlerType, extraStackFrames) + { + SecurityTokenDescriptor = new() + { + Issuer = Default.Issuer, + SigningCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2, + }; + + ValidationParameters.AlgorithmValidator = algorithmValidationDelegate; + ValidationParameters.SignatureValidator = null; + ValidationParameters.IssuerSigningKeys.Add(KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs index c358e7ad45..6a2763c5c3 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs @@ -83,6 +83,8 @@ public SecurityTokenDescriptor SecurityTokenDescriptor internal ValidationError? ValidationError { get; set; } internal int ExtraStackFrames { get; } + + internal ExpectedException? ExpectedInnerException { get; set; } } } #nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IExtensibilityTestingTokenHandler.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IExtensibilityTestingTokenHandler.cs index 62902868cd..8d2bfd5a03 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IExtensibilityTestingTokenHandler.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IExtensibilityTestingTokenHandler.cs @@ -8,6 +8,7 @@ using Microsoft.IdentityModel.Tokens.Saml; using Microsoft.IdentityModel.Tokens.Saml2; +#nullable enable namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests { // This interface is used to test the extensibility of the ValidateTokenAsync method @@ -93,3 +94,4 @@ public SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) } } } +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTestCases.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTestCases.cs index 9a6a669aa9..296708b70a 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTestCases.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTestCases.cs @@ -7,6 +7,7 @@ using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Logging; +#nullable enable namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests { public partial class ExtensibilityTesting @@ -203,3 +204,4 @@ public static TheoryData GenerateIssuerExtensibil } } } +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTheoryData.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTheoryData.cs index 12b4c53c28..329658ddde 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTheoryData.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerExtensibilityTheoryData.cs @@ -3,6 +3,7 @@ using Microsoft.IdentityModel.Tokens; +#nullable enable namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests { public class IssuerExtensibilityTheoryData : ExtensibilityTheoryData @@ -23,3 +24,4 @@ internal IssuerExtensibilityTheoryData( } } } +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerSigningKeyExtensibilityTestCases.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerSigningKeyExtensibilityTestCases.cs new file mode 100644 index 0000000000..e504f3c82a --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerSigningKeyExtensibilityTestCases.cs @@ -0,0 +1,198 @@ +// 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 GenerateIssuerSigningKeyExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames, + string stackFrameFileName) + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + string issuerGuid = Guid.NewGuid().ToString(); + + #region return CustomIssuerSigningKeyValidationError + // Test cases where delegate is overridden and return a CustomIssuerSigningKeyValidationError + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorDelegate", + tokenHandlerType, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate)), + ValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(SecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 0), + null) + }); + + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerSigningKeyException : SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorCustomExceptionDelegate", + tokenHandlerType, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate)), + ValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(CustomSecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 0), + null) + }); + + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorUnknownExceptionDelegate", + tokenHandlerType, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate, + extraStackFrames: extraStackFrames) + { + // CustomIssuerSigningKeyValidationError 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(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate))), + ValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 0), + null) + }); + + // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate", + tokenHandlerType, + CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate)), + ValidationError = new CustomIssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomIssuerSigningKeyValidationError.CustomIssuerSigningKeyValidationFailureType, + typeof(CustomSecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 0), + null), + }); + #endregion + + #region return IssuerSigningKeyValidationError + // Test cases where delegate is overridden and return an IssuerSigningKeyValidationError + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorDelegate", + tokenHandlerType, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate)), + ValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(SecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 0), + null) + }); + + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerSigningKeyException : SecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate", + tokenHandlerType, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate, + extraStackFrames: extraStackFrames) + { + // IssuerSigningKeyValidationError does not handle the exception type 'CustomSecurityTokenInvalidIssuerSigningKeyException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidSigningKeyException), + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate))), + ValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(CustomSecurityTokenInvalidSigningKeyException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 0), + null) + }); + + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorCustomExceptionTypeDelegate", + tokenHandlerType, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate, + extraStackFrames: extraStackFrames) + { + // IssuerSigningKeyValidationError 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(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate))), + ValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.SigningKeyValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 0), + null) + }); + + // IssuerSigningKeyValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException, inner: CustomSecurityTokenInvalidIssuerSigningKeyException + theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( + "IssuerSigningKeyValidatorThrows", + tokenHandlerType, + CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorThrows, + extraStackFrames: extraStackFrames - 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSigningKeyException), + string.Format(Tokens.LogMessages.IDX10274), + typeof(CustomSecurityTokenInvalidSigningKeyException)), + ValidationError = new IssuerSigningKeyValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10274), null), + ValidationFailureType.IssuerSigningKeyValidatorThrew, + typeof(SecurityTokenInvalidSigningKeyException), + new StackFrame(stackFrameFileName, 0), + null, + new SecurityTokenInvalidSigningKeyException(nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerSigningKeyExtensibilityTheoryData.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerSigningKeyExtensibilityTheoryData.cs new file mode 100644 index 0000000000..990ed5152a --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IssuerSigningKeyExtensibilityTheoryData.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.IdentityModel.Tokens; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests +{ + public class IssuerSigningKeyExtensibilityTheoryData : ExtensibilityTheoryData + { + internal IssuerSigningKeyExtensibilityTheoryData( + string testId, + string tokenHandlerType, + IssuerSigningKeyValidationDelegate issuerSigningKeyValidationDelegate, + int extraStackFrames) : base(testId, tokenHandlerType, extraStackFrames) + { + var signingCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2; + + SecurityTokenDescriptor = new() + { + Issuer = Default.Issuer, + SigningCredentials = signingCredentials, + }; + + ValidationParameters.IssuerSigningKeyValidator = issuerSigningKeyValidationDelegate; + ValidationParameters.SignatureValidator = (SecurityToken token, ValidationParameters validationParameters, BaseConfiguration? configuration, CallContext callContext) => + { + token.SigningKey = signingCredentials.Key; + + return signingCredentials.Key; + }; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/SignatureExtensibilityTestCases.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/SignatureExtensibilityTestCases.cs new file mode 100644 index 0000000000..d943f6041d --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/SignatureExtensibilityTestCases.cs @@ -0,0 +1,191 @@ +// 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 GenerateSignatureExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames, + string stackFrameFileName) + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + string issuerGuid = Guid.NewGuid().ToString(); + + #region return CustomSignatureValidationError + // Test cases where delegate is overridden and return a CustomSignatureValidationError + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorDelegate", + tokenHandlerType, + CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate)), + ValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 0)) + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorCustomExceptionDelegate", + tokenHandlerType, + CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate)), + ValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 0)), + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorUnknownExceptionDelegate", + tokenHandlerType, + CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate, + extraStackFrames: extraStackFrames) + { + // CustomSignatureValidationError 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(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate))), + ValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomSignatureValidationDelegates.cs", 0)), + }); + + // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new SignatureExtensibilityTheoryData( + "CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate", + tokenHandlerType, + CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate)), + ValidationError = new CustomSignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomSignatureValidationError.CustomSignatureValidationFailureType, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 0)), + }); + #endregion + + #region return SignatureValidationError + // Test cases where delegate is overridden and return an SignatureValidationError + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorDelegate", + tokenHandlerType, + CustomSignatureValidationDelegates.SignatureValidatorDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate)), + ValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 0)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorCustomSignatureExceptionTypeDelegate", + tokenHandlerType, + CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate, + extraStackFrames: extraStackFrames) + { + // SignatureValidationError does not handle the exception type 'CustomSecurityTokenInvalidSignatureException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidSignatureException), + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate))), + ValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenInvalidSignatureException), + new StackFrame("CustomSignatureValidationDelegates.cs", 0)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorCustomExceptionTypeDelegate", + tokenHandlerType, + CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate, + extraStackFrames: extraStackFrames) + { + // SignatureValidationError 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(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate))), + ValidationError = new SignatureValidationError( + new MessageDetail( + nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.SignatureValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomSignatureValidationDelegates.cs", 0)) + }); + + // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidSignatureException + theoryData.Add(new SignatureExtensibilityTheoryData( + "SignatureValidatorThrows", + tokenHandlerType, + CustomSignatureValidationDelegates.SignatureValidatorThrows, + extraStackFrames: extraStackFrames - 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidSignatureException), + string.Format(Tokens.LogMessages.IDX10272), + typeof(CustomSecurityTokenInvalidSignatureException)), + ValidationError = new SignatureValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10272), null), + ValidationFailureType.SignatureValidatorThrew, + typeof(SecurityTokenInvalidSignatureException), + new StackFrame(stackFrameFileName, 0), + null, // no inner validation error + new SecurityTokenInvalidSignatureException(nameof(CustomSignatureValidationDelegates.SignatureValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/SignatureExtensibilityTheoryData.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/SignatureExtensibilityTheoryData.cs new file mode 100644 index 0000000000..f3d222bae8 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/SignatureExtensibilityTheoryData.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.IdentityModel.Tokens; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests +{ + public class SignatureExtensibilityTheoryData : ExtensibilityTheoryData + { + internal SignatureExtensibilityTheoryData( + string testId, + string tokenHandlerType, + SignatureValidationDelegate signatureValidationDelegate, + int extraStackFrames) : base(testId, tokenHandlerType, extraStackFrames) + { + SecurityTokenDescriptor = new() + { + Issuer = Default.Issuer, + }; + + ValidationParameters.SignatureValidator = signatureValidationDelegate; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenReplayExtensibilityTestCases.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenReplayExtensibilityTestCases.cs new file mode 100644 index 0000000000..2828262df9 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenReplayExtensibilityTestCases.cs @@ -0,0 +1,206 @@ +// 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 GenerateTokenReplayExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames, + string stackFrameFileName) + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + DateTime expirationTime = DateTime.UtcNow + TimeSpan.FromHours(1); + + #region return CustomTokenReplayValidationError + // Test cases where delegate is overridden and return a CustomTokenReplayValidationError + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidationDelegate", + tokenHandlerType, + expirationTime, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate)), + ValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(SecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 0), + expirationTime) + }); + + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: CustomSecurityTokenReplayDetectedException : SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidatorCustomExceptionDelegate", + tokenHandlerType, + expirationTime, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate)), + ValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(CustomSecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 0), + expirationTime), + }); + + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidatorUnknownExceptionDelegate", + tokenHandlerType, + expirationTime, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate, + extraStackFrames: extraStackFrames) + { + // CustomTokenReplayValidationError 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(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate))), + ValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 0), + expirationTime), + }); + + // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate", + tokenHandlerType, + expirationTime, + CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate)), + ValidationError = new CustomTokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomTokenReplayValidationError.CustomTokenReplayValidationFailureType, + typeof(CustomSecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 0), + expirationTime), + }); + #endregion + + #region return TokenReplayValidationError + // Test cases where delegate is overridden and return an TokenReplayValidationError + // TokenReplayValidationError : ValidationError, ExceptionType: SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidationDelegate", + tokenHandlerType, + expirationTime, + CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate)), + ValidationError = new TokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(SecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 0), + expirationTime) + }); + + // TokenReplayValidationError : ValidationError, ExceptionType: CustomSecurityTokenReplayDetectedException : SecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate", + tokenHandlerType, + expirationTime, + CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate, + extraStackFrames: extraStackFrames) + { + // TokenReplayValidationError does not handle the exception type 'CustomSecurityTokenReplayDetectedException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenReplayDetectedException), + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate))), + ValidationError = new TokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(CustomSecurityTokenReplayDetectedException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 0), + expirationTime) + }); + + // TokenReplayValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidatorCustomExceptionTypeDelegate", + tokenHandlerType, + expirationTime, + CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate, + extraStackFrames: extraStackFrames) + { + // TokenReplayValidationError 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(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate))), + ValidationError = new TokenReplayValidationError( + new MessageDetail( + nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.TokenReplayValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomTokenReplayValidationDelegates.cs", 0), + expirationTime) + }); + + // TokenReplayValidationError : ValidationError, ExceptionType: SecurityTokenReplayDetectedException, inner: CustomSecurityTokenReplayDetectedException + theoryData.Add(new TokenReplayExtensibilityTheoryData( + "TokenReplayValidatorThrows", + tokenHandlerType, + expirationTime, + CustomTokenReplayValidationDelegates.TokenReplayValidatorThrows, + extraStackFrames: extraStackFrames - 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenReplayDetectedException), + string.Format(Tokens.LogMessages.IDX10276), + typeof(CustomSecurityTokenReplayDetectedException)), + ValidationError = new TokenReplayValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10276), null), + ValidationFailureType.TokenReplayValidatorThrew, + typeof(SecurityTokenReplayDetectedException), + new StackFrame(stackFrameFileName, 0), + expirationTime, + new SecurityTokenReplayDetectedException(nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenReplayExtensibilityTheoryData.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenReplayExtensibilityTheoryData.cs new file mode 100644 index 0000000000..20f2111df6 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenReplayExtensibilityTheoryData.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 TokenReplayExtensibilityTheoryData : ExtensibilityTheoryData + { + internal TokenReplayExtensibilityTheoryData( + string testId, + string tokenHandlerType, + DateTime expirationTime, + TokenReplayValidationDelegate tokenReplayValidationDelegate, + int extraStackFrames) : base(testId, tokenHandlerType, extraStackFrames) + { + SecurityTokenDescriptor = new() + { + Issuer = Default.Issuer, + IssuedAt = expirationTime.AddMinutes(-10), + NotBefore = expirationTime.AddMinutes(-5), + Expires = expirationTime, + }; + + ValidationParameters.TokenReplayValidator = tokenReplayValidationDelegate; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenTypeExtensibilityTestCases.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenTypeExtensibilityTestCases.cs new file mode 100644 index 0000000000..1afd67a6dd --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenTypeExtensibilityTestCases.cs @@ -0,0 +1,206 @@ +// 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 GenerateTokenTypeExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames, + string stackFrameFileName) + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + string tokenType = "NOTJWT"; + + #region return CustomTokenTypeValidationError + // Test cases where delegate is overridden and return a CustomTokenTypeValidationError + // CustomTokenTypeValidationError : TokenTypeValidationError, ExceptionType: SecurityTokenInvalidTypeException + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "CustomTokenTypeValidatorDelegate", + tokenHandlerType, + tokenType, + CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidTypeException), + nameof(CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorDelegate)), + ValidationError = new CustomTokenTypeValidationError( + new MessageDetail( + nameof(CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorDelegate), null), + ValidationFailureType.TokenTypeValidationFailed, + typeof(SecurityTokenInvalidTypeException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 0), + tokenType) + }); + + // CustomTokenTypeValidationError : TokenTypeValidationError, ExceptionType: CustomSecurityTokenInvalidTypeException : SecurityTokenInvalidTypeException + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "CustomTokenTypeValidatorCustomExceptionDelegate", + tokenHandlerType, + tokenType, + CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorCustomExceptionDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidTypeException), + nameof(CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorCustomExceptionDelegate)), + ValidationError = new CustomTokenTypeValidationError( + new MessageDetail( + nameof(CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorCustomExceptionDelegate), null), + ValidationFailureType.TokenTypeValidationFailed, + typeof(CustomSecurityTokenInvalidTypeException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 0), + tokenType) + }); + + // CustomTokenTypeValidationError : TokenTypeValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "CustomTokenTypeValidatorUnknownExceptionDelegate", + tokenHandlerType, + tokenType, + CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorUnknownExceptionDelegate, + extraStackFrames: extraStackFrames) + { + // 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))), + ValidationError = new CustomTokenTypeValidationError( + new MessageDetail( + nameof(CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorUnknownExceptionDelegate), null), + ValidationFailureType.TokenTypeValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 0), + tokenType) + }); + + // CustomTokenTypeValidationError : TokenTypeValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "CustomTokenTypeValidatorCustomExceptionCustomFailureTypeDelegate", + tokenHandlerType, + tokenType, + CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidTypeException), + nameof(CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorCustomExceptionCustomFailureTypeDelegate)), + ValidationError = new CustomTokenTypeValidationError( + new MessageDetail( + nameof(CustomTokenTypeValidationDelegates.CustomTokenTypeValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomTokenTypeValidationError.CustomTokenTypeValidationFailureType, + typeof(CustomSecurityTokenInvalidTypeException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 0), + tokenType), + }); + #endregion + + #region return TokenTypeValidationError + // Test cases where delegate is overridden and return an TokenTypeValidationError + // TokenTypeValidationError : ValidationError, ExceptionType: SecurityTokenInvalidTypeException + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "TokenTypeValidatorDelegate", + tokenHandlerType, + tokenType, + CustomTokenTypeValidationDelegates.TokenTypeValidatorDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidTypeException), + nameof(CustomTokenTypeValidationDelegates.TokenTypeValidatorDelegate)), + ValidationError = new TokenTypeValidationError( + new MessageDetail( + nameof(CustomTokenTypeValidationDelegates.TokenTypeValidatorDelegate), null), + ValidationFailureType.TokenTypeValidationFailed, + typeof(SecurityTokenInvalidTypeException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 0), + tokenType) + }); + + // TokenTypeValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidTypeException : SecurityTokenInvalidTypeException + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "TokenTypeValidatorCustomTokenTypeExceptionTypeDelegate", + tokenHandlerType, + tokenType, + CustomTokenTypeValidationDelegates.TokenTypeValidatorCustomTokenTypeExceptionTypeDelegate, + extraStackFrames: extraStackFrames) + { + // 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))), + ValidationError = new TokenTypeValidationError( + new MessageDetail( + nameof(CustomTokenTypeValidationDelegates.TokenTypeValidatorCustomTokenTypeExceptionTypeDelegate), null), + ValidationFailureType.TokenTypeValidationFailed, + typeof(CustomSecurityTokenInvalidTypeException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 0), + tokenType) + }); + + // TokenTypeValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "TokenTypeValidatorCustomExceptionTypeDelegate", + tokenHandlerType, + tokenType, + CustomTokenTypeValidationDelegates.TokenTypeValidatorCustomExceptionTypeDelegate, + extraStackFrames: extraStackFrames) + { + // 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))), + ValidationError = new TokenTypeValidationError( + new MessageDetail( + nameof(CustomTokenTypeValidationDelegates.TokenTypeValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.TokenTypeValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomTokenTypeValidationDelegates.cs", 0), + tokenType) + }); + + // TokenTypeValidationError : ValidationError, ExceptionType: SecurityTokenInvalidTypeException, inner: CustomSecurityTokenInvalidTypeException + theoryData.Add(new TokenTypeExtensibilityTheoryData( + "TokenTypeValidatorThrows", + tokenHandlerType, + tokenType, + CustomTokenTypeValidationDelegates.TokenTypeValidatorThrows, + extraStackFrames: extraStackFrames - 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidTypeException), + string.Format(Tokens.LogMessages.IDX10275), + typeof(CustomSecurityTokenInvalidTypeException)), + ValidationError = new TokenTypeValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10275), null), + ValidationFailureType.TokenTypeValidatorThrew, + typeof(SecurityTokenInvalidTypeException), + new StackFrame("JsonWebTokenHandler.ValidateToken.Internal.cs", 0), + tokenType, + new SecurityTokenInvalidTypeException(nameof(CustomTokenTypeValidationDelegates.TokenTypeValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenTypeExtensibilityTheoryData.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenTypeExtensibilityTheoryData.cs new file mode 100644 index 0000000000..c83ccdbd4f --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/TokenTypeExtensibilityTheoryData.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.IdentityModel.Tokens; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests +{ + public class TokenTypeExtensibilityTheoryData : ExtensibilityTheoryData + { + internal TokenTypeExtensibilityTheoryData( + string testId, + string tokenHandlerType, + string tokenType, + TokenTypeValidationDelegate tokenTypeValidationDelegate, + int extraStackFrames) : base(testId, tokenHandlerType, extraStackFrames) + { + SecurityTokenDescriptor = new() + { + Issuer = Default.Issuer, + TokenType = tokenType, + }; + + ValidationParameters.TokenTypeValidator = tokenTypeValidationDelegate; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ValidateTokenAsyncExtensibility.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ValidateTokenAsyncExtensibility.cs index f5cc14145e..240fd24e4a 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ValidateTokenAsyncExtensibility.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ValidateTokenAsyncExtensibility.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.IdentityModel.Tokens; +#nullable enable namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests { public partial class ExtensibilityTesting @@ -35,8 +36,31 @@ public static async Task ValidateTokenAsync_Extensibility(ExtensibilityTheoryDat else { ValidationError validationError = validationResult.UnwrapError(); - IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.ValidationError, context); + + if (validationError is SignatureValidationError signatureValidationError && + signatureValidationError.InnerValidationError is not null) + { + // Algorithm validation errors are wrapped in a signature validation error + // Other validation errors use the else branch. + IdentityComparer.AreValidationErrorsEqual( + signatureValidationError.InnerValidationError, + theoryData.ValidationError, + context); + } + else + { + IdentityComparer.AreValidationErrorsEqual( + validationError, + theoryData.ValidationError, + context); + } + theoryData.ExpectedException.ProcessException(validationError.GetException(), context); + + // In the algorithm validation case, we want to ensure the inner exception contains + // the expected message and not just assert its type. + if (theoryData.ExpectedInnerException is not null) + theoryData.ExpectedInnerException.ProcessException(validationError.GetException().InnerException, context); } } catch (Exception ex) @@ -48,3 +72,4 @@ public static async Task ValidateTokenAsync_Extensibility(ExtensibilityTheoryDat } } } +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Algorithm.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Algorithm.cs index b2e3171da4..4a11fc47ac 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Algorithm.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Algorithm.cs @@ -1,13 +1,8 @@ // 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.Logging; -using Microsoft.IdentityModel.TestUtils; - +using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -15,322 +10,27 @@ namespace Microsoft.IdentityModel.Tokens.Saml2.Extensibility.Tests { public partial class Saml2SecurityTokenHandlerValidateTokenAsyncTests { - [Theory, MemberData(nameof(Algorithm_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateTokenAsync_AlgorithmValidator_Extensibility(AlgorithmExtensibilityTheoryData theoryData) + [Theory, MemberData( + nameof(GenerateAlgorithmExtensibilityTestCases), + parameters: ["SAML2", 1], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_AlgorithmValidator_Extensibility( + AlgorithmExtensibilityTheoryData theoryData) { - var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_AlgorithmValidator_Extensibility)}", theoryData); - context.IgnoreType = false; - for (int i = 0; i < theoryData.ExtraStackFrames; i++) - theoryData.ValidationError!.AddStackFrame(new StackFrame(false)); - - Saml2SecurityToken saml2Token = (Saml2SecurityToken)theoryData.Saml2SecurityTokenHandler.CreateToken(new SecurityTokenDescriptor() - { - Issuer = Default.Issuer, - Subject = Default.SamlClaimsIdentity, - SigningCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2, - }); - theoryData.Saml2Token = theoryData.Saml2SecurityTokenHandler.ReadSaml2Token(saml2Token.Assertion.CanonicalString); - - theoryData.ValidationParameters!.IssuerSigningKeys.Add(theoryData.SigningKey); - - try - { - ValidationResult validationResult = await theoryData.Saml2SecurityTokenHandler.ValidateTokenAsync( - theoryData.Saml2Token!, - theoryData.ValidationParameters!, - theoryData.CallContext, - CancellationToken.None); - - if (validationResult.IsValid) - { - // We expect the validation to fail, but it passed - context.AddDiff("ValidationResult is Valid."); - } - else - { - ValidationError validationError = validationResult.UnwrapError(); - - if (validationError is SignatureValidationError signatureValidationError && - signatureValidationError.InnerValidationError is not null) - { - IdentityComparer.AreValidationErrorsEqual( - signatureValidationError.InnerValidationError, - theoryData.ValidationError, - context); - } - else - { - IdentityComparer.AreValidationErrorsEqual( - validationError, - theoryData.ValidationError, - context); - } - - var exception = validationError.GetException(); - theoryData.ExpectedException.ProcessException(exception, context); - // Process the inner exception since invalid algorithm exceptions are wrapped inside - // invalid signature exceptions - if (theoryData.ExpectedInnerException is not null) - theoryData.ExpectedInnerException.ProcessException(exception.InnerException, context); - } - } - catch (Exception ex) - { - // We expect the validation to fail, but it threw an exception - context.AddDiff($"ValidateTokenAsync threw exception: {ex}"); - } - - TestUtilities.AssertFailIfErrors(context); + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_AlgorithmValidator_Extensibility)); } - public static TheoryData Algorithm_ExtensibilityTestCases + public static TheoryData GenerateAlgorithmExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - get - { - var theoryData = new TheoryData(); - CallContext callContext = new CallContext(); - var utcNow = DateTime.UtcNow; - var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); - - #region return CustomAlgorithmValidationError - // Test cases where delegate is overridden and return a CustomAlgorithmValidationError - // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "CustomAlgorithmValidatorDelegate", - utcNow, - CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(SecurityTokenInvalidAlgorithmException)), - ExpectedInnerException = new ExpectedException( - typeof(SecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate)), - ValidationError = new CustomAlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(SecurityTokenInvalidAlgorithmException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 160), - "algorithm") - }); - - // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "CustomAlgorithmValidatorCustomExceptionDelegate", - utcNow, - CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(CustomSecurityTokenInvalidAlgorithmException)), - ExpectedInnerException = new ExpectedException( - typeof(CustomSecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate)), - ValidationError = new CustomAlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(CustomSecurityTokenInvalidAlgorithmException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 175), - "algorithm"), - }); - - // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "CustomAlgorithmValidatorUnknownExceptionDelegate", - utcNow, - CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate, - extraStackFrames: 1) - { - // CustomAlgorithmValidationError does not handle the exception type 'NotSupportedException' - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(SecurityTokenException)), - ExpectedInnerException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(NotSupportedException), - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate))), - ValidationError = new CustomAlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(NotSupportedException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 205), - "algorithm"), - }); - - // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate", - utcNow, - CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(CustomSecurityTokenInvalidAlgorithmException)), - ExpectedInnerException = new ExpectedException( - typeof(CustomSecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate)), - ValidationError = new CustomAlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate), null), - CustomAlgorithmValidationError.CustomAlgorithmValidationFailureType, - typeof(CustomSecurityTokenInvalidAlgorithmException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 190), - "algorithm"), - }); - #endregion - - #region return AlgorithmValidationError - // Test cases where delegate is overridden and return an AlgorithmValidationError - // AlgorithmValidationError : ValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "AlgorithmValidatorDelegate", - utcNow, - CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(SecurityTokenInvalidAlgorithmException)), - ExpectedInnerException = new ExpectedException( - typeof(SecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate)), - ValidationError = new AlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(SecurityTokenInvalidAlgorithmException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 235), - "algorithm") - }); - - // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate", - utcNow, - CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate, - extraStackFrames: 1) - { - // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenInvalidAlgorithmException' - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(SecurityTokenException)), - ExpectedInnerException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate))), - ValidationError = new AlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(CustomSecurityTokenInvalidAlgorithmException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 259), - "algorithm") - }); - - // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "AlgorithmValidatorCustomExceptionTypeDelegate", - utcNow, - CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate, - extraStackFrames: 1) - { - // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenException' - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(SecurityTokenException)), - ExpectedInnerException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenException), - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate))), - ValidationError = new AlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(CustomSecurityTokenException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 274), - "algorithm") - }); - - // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidAlgorithmException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "AlgorithmValidatorThrows", - utcNow, - CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10273:", - typeof(CustomSecurityTokenInvalidAlgorithmException)), - ExpectedInnerException = new ExpectedException( - typeof(CustomSecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows)), - ValidationError = new SignatureValidationError( - new MessageDetail( - string.Format(Tokens.LogMessages.IDX10273), null), - ValidationFailureType.AlgorithmValidatorThrew, - typeof(SecurityTokenInvalidSignatureException), - new StackFrame("Saml2SecurityTokenHandler.ValidateSignature.cs", 250), - null, // no inner validation error - new CustomSecurityTokenInvalidAlgorithmException(nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows), null) - ) - }); - #endregion - - return theoryData; - } - } - - public class AlgorithmExtensibilityTheoryData : TheoryDataBase - { - internal AlgorithmExtensibilityTheoryData(string testId, DateTime utcNow, AlgorithmValidationDelegate algorithmValidator, int extraStackFrames) : base(testId) - { - ValidationParameters = new ValidationParameters - { - // We leave the default signature validator to call the custom algorithm validator - AlgorithmValidator = algorithmValidator, - AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, - IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, - IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, - LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, - TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, - TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation, - }; - - ExtraStackFrames = extraStackFrames; - } - - public Saml2SecurityToken? Saml2Token { get; set; } - - public Saml2SecurityTokenHandler Saml2SecurityTokenHandler { get; } = new Saml2SecurityTokenHandler(); - - public bool IsValid { get; set; } - - internal ValidationError? ValidationError { get; set; } - - public ExpectedException? ExpectedInnerException { get; set; } - - internal int ExtraStackFrames { get; } - - internal ValidationParameters ValidationParameters { get; } - - public SecurityKey SigningKey { get; set; } = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key; + return ExtensibilityTesting.GenerateAlgorithmExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "Saml2SecurityTokenHandler.ValidateSignature.cs"); } } } diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.IssuerSigningKey.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.IssuerSigningKey.cs index 95c1eecfd6..9bc70cdbb8 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.IssuerSigningKey.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.IssuerSigningKey.cs @@ -1,13 +1,8 @@ // 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.Logging; -using Microsoft.IdentityModel.TestUtils; - +using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -15,268 +10,27 @@ namespace Microsoft.IdentityModel.Tokens.Saml2.Extensibility.Tests { public partial class Saml2SecurityTokenHandlerValidateTokenAsyncTests { - [Theory, MemberData(nameof(IssuerSigningKey_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility(IssuerSigningKeyExtensibilityTheoryData theoryData) - { - var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility)}", theoryData); - context.IgnoreType = false; - for (int i = 0; i < theoryData.ExtraStackFrames; i++) - theoryData.IssuerSigningKeyValidationError!.AddStackFrame(new StackFrame(false)); - - try - { - ValidationResult validationResult = await theoryData.Saml2SecurityTokenHandler.ValidateTokenAsync( - theoryData.Saml2Token!, - theoryData.ValidationParameters!, - theoryData.CallContext, - CancellationToken.None); - - if (validationResult.IsValid) - { - context.AddDiff("validationResult.IsValid is true, expected false"); - } - else - { - ValidationError validationError = validationResult.UnwrapError(); - IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.IssuerSigningKeyValidationError, context); - theoryData.ExpectedException.ProcessException(validationError.GetException(), context); - } - } - catch (Exception ex) - { - theoryData.ExpectedException.ProcessException(ex, context); - } - - TestUtilities.AssertFailIfErrors(context); - } - - public static TheoryData IssuerSigningKey_ExtensibilityTestCases + [Theory, MemberData( + nameof(GenerateIssuerSigningKeyExtensibilityTestCases), + parameters: ["SAML2", 1], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility( + IssuerSigningKeyExtensibilityTheoryData theoryData) { - get - { - var theoryData = new TheoryData(); - CallContext callContext = new CallContext(); - var utcNow = DateTime.UtcNow; - var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); - - #region return CustomIssuerSigningKeyValidationError - // Test cases where delegate is overridden and return a CustomIssuerSigningKeyValidationError - // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "CustomIssuerSigningKeyValidatorDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSigningKeyException), - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate)), - IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(SecurityTokenInvalidSigningKeyException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 160), - null) - }); - - // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerSigningKeyException : SecurityTokenInvalidIssuerSigningKeyException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "CustomIssuerSigningKeyValidatorCustomExceptionDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidSigningKeyException), - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate)), - IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(CustomSecurityTokenInvalidSigningKeyException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 175), - null) - }); - - // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: NotSupportedException : SystemException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "CustomIssuerSigningKeyValidatorUnknownExceptionDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate, - extraStackFrames: 1) - { - // CustomIssuerSigningKeyValidationError 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(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate))), - IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(NotSupportedException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 205), - null) - }); - - // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidSigningKeyException), - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate)), - IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate), null), - CustomIssuerSigningKeyValidationError.CustomIssuerSigningKeyValidationFailureType, - typeof(CustomSecurityTokenInvalidSigningKeyException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 190), - null), - }); - #endregion - - #region return IssuerSigningKeyValidationError - // Test cases where delegate is overridden and return an IssuerSigningKeyValidationError - // IssuerSigningKeyValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "IssuerSigningKeyValidatorDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSigningKeyException), - nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate)), - IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(SecurityTokenInvalidSigningKeyException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 235), - null) - }); - - // IssuerSigningKeyValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerSigningKeyException : SecurityTokenInvalidIssuerSigningKeyException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate, - extraStackFrames: 1) - { - // IssuerSigningKeyValidationError does not handle the exception type 'CustomSecurityTokenInvalidIssuerSigningKeyException' - ExpectedException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenInvalidSigningKeyException), - nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate))), - IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(CustomSecurityTokenInvalidSigningKeyException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 259), - null) - }); - - // IssuerSigningKeyValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "IssuerSigningKeyValidatorCustomExceptionTypeDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate, - extraStackFrames: 1) - { - // IssuerSigningKeyValidationError 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(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate))), - IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(CustomSecurityTokenException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 274), - null) - }); - - // IssuerSigningKeyValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException, inner: CustomSecurityTokenInvalidIssuerSigningKeyException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "IssuerSigningKeyValidatorThrows", - utcNow, - CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorThrows, - extraStackFrames: 0) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSigningKeyException), - string.Format(Tokens.LogMessages.IDX10274), - typeof(CustomSecurityTokenInvalidSigningKeyException)), - IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( - new MessageDetail( - string.Format(Tokens.LogMessages.IDX10274), null), - ValidationFailureType.IssuerSigningKeyValidatorThrew, - typeof(SecurityTokenInvalidSigningKeyException), - new StackFrame("Saml2SecurityTokenHandler.ValidateToken.Internal.cs", 250), - null, - new SecurityTokenInvalidSigningKeyException(nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorThrows)) - ) - }); - #endregion - - return theoryData; - } + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility)); } - public class IssuerSigningKeyExtensibilityTheoryData : TheoryDataBase + public static TheoryData GenerateIssuerSigningKeyExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - internal IssuerSigningKeyExtensibilityTheoryData(string testId, DateTime utcNow, IssuerSigningKeyValidationDelegate issuerSigningKeyValidator, int extraStackFrames) : base(testId) - { - Saml2Token = (Saml2SecurityToken)Saml2SecurityTokenHandler.CreateToken( - new SecurityTokenDescriptor() - { - Subject = Default.SamlClaimsIdentity, - Issuer = Default.Issuer, - }); - - ValidationParameters = new ValidationParameters - { - AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, - AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, - IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, - IssuerSigningKeyValidator = issuerSigningKeyValidator, - LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, - SignatureValidator = (SecurityToken token, ValidationParameters validationParameters, BaseConfiguration? configuration, CallContext callContext) => - { - token.SigningKey = SigningKey; - - return SigningKey; - }, - TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, - TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation - }; - - ExtraStackFrames = extraStackFrames; - } - - public Saml2SecurityToken Saml2Token { get; } - - public Saml2SecurityTokenHandler Saml2SecurityTokenHandler { get; } = new Saml2SecurityTokenHandler(); - - public bool IsValid { get; set; } - - public SecurityKey SigningKey { get; set; } = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key; - - internal ValidationParameters? ValidationParameters { get; set; } - - internal IssuerSigningKeyValidationError? IssuerSigningKeyValidationError { get; set; } - - internal int ExtraStackFrames { get; } + return ExtensibilityTesting.GenerateIssuerSigningKeyExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "Saml2SecurityTokenHandler.ValidateToken.Internal.cs"); } } } diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Signature.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Signature.cs index d1635c6635..7f39a236ca 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Signature.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Signature.cs @@ -1,13 +1,8 @@ // 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.Logging; -using Microsoft.IdentityModel.TestUtils; - +using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -15,255 +10,27 @@ namespace Microsoft.IdentityModel.Tokens.Saml2.Extensibility.Tests { public partial class Saml2SecurityTokenHandlerValidateTokenAsyncTests { - [Theory, MemberData(nameof(Signature_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateTokenAsync_SignatureValidator_Extensibility(SignatureExtensibilityTheoryData theoryData) - { - var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_SignatureValidator_Extensibility)}", theoryData); - context.IgnoreType = false; - for (int i = 0; i < theoryData.ExtraStackFrames; i++) - theoryData.SignatureValidationError!.AddStackFrame(new StackFrame(false)); - - try - { - ValidationResult validationResult = await theoryData.Saml2SecurityTokenHandler.ValidateTokenAsync( - theoryData.Saml2Token!, - theoryData.ValidationParameters!, - theoryData.CallContext, - CancellationToken.None); - - if (validationResult.IsValid) - { - // We expect the validation to fail, but it passed - context.AddDiff("ValidationResult is Valid."); - } - else - { - ValidationError validationError = validationResult.UnwrapError(); - IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.SignatureValidationError, context); - theoryData.ExpectedException.ProcessException(validationError.GetException(), context); - } - } - catch (Exception ex) - { - theoryData.ExpectedException.ProcessException(ex, context); - } - - TestUtilities.AssertFailIfErrors(context); - } - - public static TheoryData Signature_ExtensibilityTestCases + [Theory, MemberData( + nameof(GenerateSignatureExtensibilityTestCases), + parameters: ["SAML2", 2], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_SignatureValidator_Extensibility( + SignatureExtensibilityTheoryData theoryData) { - get - { - var theoryData = new TheoryData(); - CallContext callContext = new CallContext(); - var utcNow = DateTime.UtcNow; - var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); - - #region return CustomSignatureValidationError - // Test cases where delegate is overridden and return a CustomSignatureValidationError - // CustomSignatureValidationError : SignatureValidationError, ExceptionType: SecurityTokenInvalidSignatureException - theoryData.Add(new SignatureExtensibilityTheoryData( - "CustomSignatureValidatorDelegate", - utcNow, - CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate)), - SignatureValidationError = new CustomSignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(SecurityTokenInvalidSignatureException), - new StackFrame("CustomSignatureValidationDelegates.cs", 160)) - }); - - // CustomSignatureValidationError : SignatureValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException - theoryData.Add(new SignatureExtensibilityTheoryData( - "CustomSignatureValidatorCustomExceptionDelegate", - utcNow, - CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidSignatureException), - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate)), - SignatureValidationError = new CustomSignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(CustomSecurityTokenInvalidSignatureException), - new StackFrame("CustomSignatureValidationDelegates.cs", 175)), - }); - - // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException - theoryData.Add(new SignatureExtensibilityTheoryData( - "CustomSignatureValidatorUnknownExceptionDelegate", - utcNow, - CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate, - extraStackFrames: 2) - { - // CustomSignatureValidationError 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(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate))), - SignatureValidationError = new CustomSignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(NotSupportedException), - new StackFrame("CustomSignatureValidationDelegates.cs", 205)), - }); - - // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType - theoryData.Add(new SignatureExtensibilityTheoryData( - "CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate", - utcNow, - CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidSignatureException), - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate)), - SignatureValidationError = new CustomSignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate), null), - CustomSignatureValidationError.CustomSignatureValidationFailureType, - typeof(CustomSecurityTokenInvalidSignatureException), - new StackFrame("CustomSignatureValidationDelegates.cs", 190)), - }); - #endregion - - #region return SignatureValidationError - // Test cases where delegate is overridden and return an SignatureValidationError - // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException - theoryData.Add(new SignatureExtensibilityTheoryData( - "SignatureValidatorDelegate", - utcNow, - CustomSignatureValidationDelegates.SignatureValidatorDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate)), - SignatureValidationError = new SignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(SecurityTokenInvalidSignatureException), - new StackFrame("CustomSignatureValidationDelegates.cs", 235)) - }); - - // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException - theoryData.Add(new SignatureExtensibilityTheoryData( - "SignatureValidatorCustomSignatureExceptionTypeDelegate", - utcNow, - CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate, - extraStackFrames: 2) - { - // SignatureValidationError does not handle the exception type 'CustomSecurityTokenInvalidSignatureException' - ExpectedException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenInvalidSignatureException), - nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate))), - SignatureValidationError = new SignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(CustomSecurityTokenInvalidSignatureException), - new StackFrame("CustomSignatureValidationDelegates.cs", 259)) - }); - - // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException - theoryData.Add(new SignatureExtensibilityTheoryData( - "SignatureValidatorCustomExceptionTypeDelegate", - utcNow, - CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate, - extraStackFrames: 2) - { - // SignatureValidationError 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(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate))), - SignatureValidationError = new SignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(CustomSecurityTokenException), - new StackFrame("CustomSignatureValidationDelegates.cs", 274)) - }); - - // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidSignatureException - theoryData.Add(new SignatureExtensibilityTheoryData( - "SignatureValidatorThrows", - utcNow, - CustomSignatureValidationDelegates.SignatureValidatorThrows, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - string.Format(Tokens.LogMessages.IDX10272), - typeof(CustomSecurityTokenInvalidSignatureException)), - SignatureValidationError = new SignatureValidationError( - new MessageDetail( - string.Format(Tokens.LogMessages.IDX10272), null), - ValidationFailureType.SignatureValidatorThrew, - typeof(SecurityTokenInvalidSignatureException), - new StackFrame("Saml2SecurityTokenHandler.ValidateSignature.cs", 250), - null, // no inner validation error - new SecurityTokenInvalidSignatureException(nameof(CustomSignatureValidationDelegates.SignatureValidatorThrows)) - ) - }); - #endregion - - return theoryData; - } + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_SignatureValidator_Extensibility)); } - public class SignatureExtensibilityTheoryData : TheoryDataBase + public static TheoryData GenerateSignatureExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - internal SignatureExtensibilityTheoryData(string testId, DateTime utcNow, SignatureValidationDelegate signatureValidator, int extraStackFrames) : base(testId) - { - Saml2Token = (Saml2SecurityToken)Saml2SecurityTokenHandler.CreateToken( - new SecurityTokenDescriptor() - { - Subject = Default.SamlClaimsIdentity, - Issuer = Default.Issuer, - }); - - ValidationParameters = new ValidationParameters - { - AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, - AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, - IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, - IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, - LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, - SignatureValidator = signatureValidator, - TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, - TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation - }; - - ExtraStackFrames = extraStackFrames; - } - - public Saml2SecurityToken Saml2Token { get; } - - public Saml2SecurityTokenHandler Saml2SecurityTokenHandler { get; } = new Saml2SecurityTokenHandler(); - - public bool IsValid { get; set; } - - internal ValidationParameters ValidationParameters { get; } - - internal SignatureValidationError? SignatureValidationError { get; set; } - - internal int ExtraStackFrames { get; } + return ExtensibilityTesting.GenerateSignatureExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "Saml2SecurityTokenHandler.ValidateSignature.cs"); } } } diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.TokenReplay.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.TokenReplay.cs index 3266a4595a..8e801b04a9 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.TokenReplay.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.TokenReplay.cs @@ -1,13 +1,8 @@ // 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.Logging; -using Microsoft.IdentityModel.TestUtils; - +using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -15,264 +10,27 @@ namespace Microsoft.IdentityModel.Tokens.Saml2.Extensibility.Tests { public partial class Saml2SecurityTokenHandlerValidateTokenAsyncTests { - [Theory, MemberData(nameof(TokenReplay_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateTokenAsync_TokenReplayValidator_Extensibility(TokenReplayExtensibilityTheoryData theoryData) - { - var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_TokenReplayValidator_Extensibility)}", theoryData); - context.IgnoreType = false; - for (int i = 0; i < theoryData.ExtraStackFrames; i++) - theoryData.TokenReplayValidationError!.AddStackFrame(new StackFrame(false)); - - try - { - ValidationResult validationResult = await theoryData.Saml2SecurityTokenHandler.ValidateTokenAsync( - theoryData.Saml2Token!, - 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.TokenReplayValidationError, context); - theoryData.ExpectedException.ProcessException(validationError.GetException(), context); - } - } - catch (Exception ex) - { - theoryData.ExpectedException.ProcessException(ex, context); - } - - TestUtilities.AssertFailIfErrors(context); - } - - public static TheoryData TokenReplay_ExtensibilityTestCases + [Theory, MemberData( + nameof(GenerateTokenReplayExtensibilityTestCases), + parameters: ["SAML2", 1], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_TokenReplayValidator_Extensibility( + TokenReplayExtensibilityTheoryData theoryData) { - get - { - var theoryData = new TheoryData(); - CallContext callContext = new CallContext(); - var utcNow = DateTime.UtcNow; - var expirationTime = utcNow + TimeSpan.FromHours(1); - - #region return CustomTokenReplayValidationError - // Test cases where delegate is overridden and return a CustomTokenReplayValidationError - // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: SecurityTokenReplayDetectedException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "CustomTokenReplayValidationDelegate", - utcNow, - CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenReplayDetectedException), - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate)), - TokenReplayValidationError = new CustomTokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(SecurityTokenReplayDetectedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 160), - expirationTime) - }); - - // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: CustomSecurityTokenReplayDetectedException : SecurityTokenReplayDetectedException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "CustomTokenReplayValidatorCustomExceptionDelegate", - utcNow, - CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenReplayDetectedException), - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate)), - TokenReplayValidationError = new CustomTokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(CustomSecurityTokenReplayDetectedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 175), - expirationTime), - }); - - // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: NotSupportedException : SystemException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "CustomTokenReplayValidatorUnknownExceptionDelegate", - utcNow, - CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate, - extraStackFrames: 1) - { - // CustomTokenReplayValidationError 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(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate))), - TokenReplayValidationError = new CustomTokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(NotSupportedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 205), - expirationTime), - }); - - // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate", - utcNow, - CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenReplayDetectedException), - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate)), - TokenReplayValidationError = new CustomTokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate), null), - CustomTokenReplayValidationError.CustomTokenReplayValidationFailureType, - typeof(CustomSecurityTokenReplayDetectedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 190), - expirationTime), - }); - #endregion - - #region return TokenReplayValidationError - // Test cases where delegate is overridden and return an TokenReplayValidationError - // TokenReplayValidationError : ValidationError, ExceptionType: SecurityTokenReplayDetectedException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "TokenReplayValidationDelegate", - utcNow, - CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenReplayDetectedException), - nameof(CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate)), - TokenReplayValidationError = new TokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(SecurityTokenReplayDetectedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 235), - expirationTime) - }); - - // TokenReplayValidationError : ValidationError, ExceptionType: CustomSecurityTokenReplayDetectedException : SecurityTokenReplayDetectedException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate", - utcNow, - CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate, - extraStackFrames: 1) - { - // TokenReplayValidationError does not handle the exception type 'CustomSecurityTokenReplayDetectedException' - ExpectedException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenReplayDetectedException), - nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate))), - TokenReplayValidationError = new TokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(CustomSecurityTokenReplayDetectedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 259), - expirationTime) - }); - - // TokenReplayValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "TokenReplayValidatorCustomExceptionTypeDelegate", - utcNow, - CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate, - extraStackFrames: 1) - { - // TokenReplayValidationError 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(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate))), - TokenReplayValidationError = new TokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(CustomSecurityTokenException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 274), - expirationTime) - }); - - // TokenReplayValidationError : ValidationError, ExceptionType: SecurityTokenReplayDetectedException, inner: CustomSecurityTokenReplayDetectedException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "TokenReplayValidatorThrows", - utcNow, - CustomTokenReplayValidationDelegates.TokenReplayValidatorThrows, - extraStackFrames: 0) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenReplayDetectedException), - string.Format(Tokens.LogMessages.IDX10276), - typeof(CustomSecurityTokenReplayDetectedException)), - TokenReplayValidationError = new TokenReplayValidationError( - new MessageDetail( - string.Format(Tokens.LogMessages.IDX10276), null), - ValidationFailureType.TokenReplayValidatorThrew, - typeof(SecurityTokenReplayDetectedException), - new StackFrame("Saml2SecurityTokenHandler.ValidateToken.Internal.cs", 250), - expirationTime, - new SecurityTokenReplayDetectedException(nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorThrows)) - ) - }); - #endregion - - return theoryData; - } + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_TokenReplayValidator_Extensibility)); } - public class TokenReplayExtensibilityTheoryData : TheoryDataBase + public static TheoryData GenerateTokenReplayExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - internal TokenReplayExtensibilityTheoryData(string testId, DateTime utcNow, TokenReplayValidationDelegate tokenReplayValidator, int extraStackFrames) : base(testId) - { - Saml2Token = (Saml2SecurityToken)Saml2SecurityTokenHandler.CreateToken( - new SecurityTokenDescriptor() - { - Subject = Default.SamlClaimsIdentity, - Issuer = Default.Issuer, - IssuedAt = utcNow, - NotBefore = utcNow, - Expires = utcNow + TimeSpan.FromHours(1), - }); - - ValidationParameters = new ValidationParameters - { - AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, - AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, - IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, - IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, - LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, - SignatureValidator = SkipValidationDelegates.SkipSignatureValidation, - TokenReplayValidator = tokenReplayValidator, - TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation - }; - - ExtraStackFrames = extraStackFrames; - } - - public Saml2SecurityToken Saml2Token { get; } - - public Saml2SecurityTokenHandler Saml2SecurityTokenHandler { get; } = new Saml2SecurityTokenHandler(); - - public bool IsValid { get; set; } - - internal ValidationParameters? ValidationParameters { get; set; } - - internal TokenReplayValidationError? TokenReplayValidationError { get; set; } - - internal int ExtraStackFrames { get; } + return ExtensibilityTesting.GenerateTokenReplayExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "Saml2SecurityTokenHandler.ValidateToken.Internal.cs"); } } } diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Algorithm.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Algorithm.cs index ade781d8ac..bdf117bece 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Algorithm.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Algorithm.cs @@ -1,13 +1,8 @@ // 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.Logging; -using Microsoft.IdentityModel.TestUtils; - +using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -15,322 +10,27 @@ namespace Microsoft.IdentityModel.Tokens.Saml.Extensibility.Tests { public partial class SamlSecurityTokenHandlerValidateTokenAsyncTests { - [Theory, MemberData(nameof(Algorithm_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateTokenAsync_AlgorithmValidator_Extensibility(AlgorithmExtensibilityTheoryData theoryData) + [Theory, MemberData( + nameof(GenerateAlgorithmExtensibilityTestCases), + parameters: ["SAML", 1], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_AlgorithmValidator_Extensibility( + AlgorithmExtensibilityTheoryData theoryData) { - var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_AlgorithmValidator_Extensibility)}", theoryData); - context.IgnoreType = false; - for (int i = 0; i < theoryData.ExtraStackFrames; i++) - theoryData.ValidationError!.AddStackFrame(new StackFrame(false)); - - SamlSecurityToken SamlToken = (SamlSecurityToken)theoryData.SamlSecurityTokenHandler.CreateToken(new SecurityTokenDescriptor() - { - Issuer = Default.Issuer, - Subject = Default.SamlClaimsIdentity, - SigningCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2, - }); - theoryData.SamlToken = theoryData.SamlSecurityTokenHandler.ReadSamlToken(SamlToken.Assertion.CanonicalString); - - theoryData.ValidationParameters!.IssuerSigningKeys.Add(theoryData.SigningKey); - - try - { - ValidationResult validationResult = await theoryData.SamlSecurityTokenHandler.ValidateTokenAsync( - theoryData.SamlToken!, - theoryData.ValidationParameters!, - theoryData.CallContext, - CancellationToken.None); - - if (validationResult.IsValid) - { - // We expect the validation to fail, but it passed - context.AddDiff("ValidationResult is Valid."); - } - else - { - ValidationError validationError = validationResult.UnwrapError(); - - if (validationError is SignatureValidationError signatureValidationError && - signatureValidationError.InnerValidationError is not null) - { - IdentityComparer.AreValidationErrorsEqual( - signatureValidationError.InnerValidationError, - theoryData.ValidationError, - context); - } - else - { - IdentityComparer.AreValidationErrorsEqual( - validationError, - theoryData.ValidationError, - context); - } - - var exception = validationError.GetException(); - theoryData.ExpectedException.ProcessException(exception, context); - // Process the inner exception since invalid algorithm exceptions are wrapped inside - // invalid signature exceptions - if (theoryData.ExpectedInnerException is not null) - theoryData.ExpectedInnerException.ProcessException(exception.InnerException, context); - } - } - catch (Exception ex) - { - // We expect the validation to fail, but it threw an exception - context.AddDiff($"ValidateTokenAsync threw exception: {ex}"); - } - - TestUtilities.AssertFailIfErrors(context); + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_AlgorithmValidator_Extensibility)); } - public static TheoryData Algorithm_ExtensibilityTestCases + public static TheoryData GenerateAlgorithmExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - get - { - var theoryData = new TheoryData(); - CallContext callContext = new CallContext(); - var utcNow = DateTime.UtcNow; - var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); - - #region return CustomAlgorithmValidationError - // Test cases where delegate is overridden and return a CustomAlgorithmValidationError - // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "CustomAlgorithmValidatorDelegate", - utcNow, - CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(SecurityTokenInvalidAlgorithmException)), - ExpectedInnerException = new ExpectedException( - typeof(SecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate)), - ValidationError = new CustomAlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(SecurityTokenInvalidAlgorithmException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 160), - "algorithm") - }); - - // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "CustomAlgorithmValidatorCustomExceptionDelegate", - utcNow, - CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(CustomSecurityTokenInvalidAlgorithmException)), - ExpectedInnerException = new ExpectedException( - typeof(CustomSecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate)), - ValidationError = new CustomAlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(CustomSecurityTokenInvalidAlgorithmException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 175), - "algorithm"), - }); - - // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "CustomAlgorithmValidatorUnknownExceptionDelegate", - utcNow, - CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate, - extraStackFrames: 1) - { - // CustomAlgorithmValidationError does not handle the exception type 'NotSupportedException' - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(SecurityTokenException)), - ExpectedInnerException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(NotSupportedException), - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate))), - ValidationError = new CustomAlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorUnknownExceptionDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(NotSupportedException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 205), - "algorithm"), - }); - - // CustomAlgorithmValidationError : AlgorithmValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate", - utcNow, - CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(CustomSecurityTokenInvalidAlgorithmException)), - ExpectedInnerException = new ExpectedException( - typeof(CustomSecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate)), - ValidationError = new CustomAlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.CustomAlgorithmValidatorCustomExceptionCustomFailureTypeDelegate), null), - CustomAlgorithmValidationError.CustomAlgorithmValidationFailureType, - typeof(CustomSecurityTokenInvalidAlgorithmException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 190), - "algorithm"), - }); - #endregion - - #region return AlgorithmValidationError - // Test cases where delegate is overridden and return an AlgorithmValidationError - // AlgorithmValidationError : ValidationError, ExceptionType: SecurityTokenInvalidAlgorithmException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "AlgorithmValidatorDelegate", - utcNow, - CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(SecurityTokenInvalidAlgorithmException)), - ExpectedInnerException = new ExpectedException( - typeof(SecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate)), - ValidationError = new AlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(SecurityTokenInvalidAlgorithmException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 235), - "algorithm") - }); - - // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidAlgorithmException : SecurityTokenInvalidAlgorithmException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate", - utcNow, - CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate, - extraStackFrames: 1) - { - // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenInvalidAlgorithmException' - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(SecurityTokenException)), - ExpectedInnerException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate))), - ValidationError = new AlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomAlgorithmExceptionTypeDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(CustomSecurityTokenInvalidAlgorithmException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 259), - "algorithm") - }); - - // AlgorithmValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "AlgorithmValidatorCustomExceptionTypeDelegate", - utcNow, - CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate, - extraStackFrames: 1) - { - // AlgorithmValidationError does not handle the exception type 'CustomSecurityTokenException' - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10518:", - typeof(SecurityTokenException)), - ExpectedInnerException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenException), - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate))), - ValidationError = new AlgorithmValidationError( - new MessageDetail( - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorCustomExceptionTypeDelegate), null), - ValidationFailureType.AlgorithmValidationFailed, - typeof(CustomSecurityTokenException), - new StackFrame("CustomAlgorithmValidationDelegates.cs", 274), - "algorithm") - }); - - // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidAlgorithmException - theoryData.Add(new AlgorithmExtensibilityTheoryData( - "AlgorithmValidatorThrows", - utcNow, - CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - "IDX10273:", - typeof(CustomSecurityTokenInvalidAlgorithmException)), - ExpectedInnerException = new ExpectedException( - typeof(CustomSecurityTokenInvalidAlgorithmException), - nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows)), - ValidationError = new SignatureValidationError( - new MessageDetail( - string.Format(Tokens.LogMessages.IDX10273), null), - ValidationFailureType.AlgorithmValidatorThrew, - typeof(SecurityTokenInvalidSignatureException), - new StackFrame("SamlSecurityTokenHandler.ValidateSignature.cs", 250), - null, // no inner validation error - new CustomSecurityTokenInvalidAlgorithmException(nameof(CustomAlgorithmValidationDelegates.AlgorithmValidatorThrows), null) - ) - }); - #endregion - - return theoryData; - } - } - - public class AlgorithmExtensibilityTheoryData : TheoryDataBase - { - internal AlgorithmExtensibilityTheoryData(string testId, DateTime utcNow, AlgorithmValidationDelegate algorithmValidator, int extraStackFrames) : base(testId) - { - ValidationParameters = new ValidationParameters - { - // We leave the default signature validator to call the custom algorithm validator - AlgorithmValidator = algorithmValidator, - AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, - IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, - IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, - LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, - TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, - TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation, - }; - - ExtraStackFrames = extraStackFrames; - } - - public SamlSecurityToken? SamlToken { get; set; } - - public SamlSecurityTokenHandler SamlSecurityTokenHandler { get; } = new SamlSecurityTokenHandler(); - - public bool IsValid { get; set; } - - internal ValidationError? ValidationError { get; set; } - - public ExpectedException? ExpectedInnerException { get; set; } - - internal int ExtraStackFrames { get; } - - internal ValidationParameters ValidationParameters { get; } - - public SecurityKey SigningKey { get; set; } = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key; + return ExtensibilityTesting.GenerateAlgorithmExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "SamlSecurityTokenHandler.ValidateSignature.cs"); } } } diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.IssuerSigningKey.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.IssuerSigningKey.cs index a6d68dda0e..2c0bf99497 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.IssuerSigningKey.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.IssuerSigningKey.cs @@ -1,13 +1,8 @@ // 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.Logging; -using Microsoft.IdentityModel.TestUtils; - +using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -15,268 +10,27 @@ namespace Microsoft.IdentityModel.Tokens.Saml.Extensibility.Tests { public partial class SamlSecurityTokenHandlerValidateTokenAsyncTests { - [Theory, MemberData(nameof(IssuerSigningKey_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility(IssuerSigningKeyExtensibilityTheoryData theoryData) - { - var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility)}", theoryData); - context.IgnoreType = false; - for (int i = 0; i < theoryData.ExtraStackFrames; i++) - theoryData.IssuerSigningKeyValidationError!.AddStackFrame(new StackFrame(false)); - - try - { - ValidationResult validationResult = await theoryData.SamlSecurityTokenHandler.ValidateTokenAsync( - theoryData.SamlToken!, - theoryData.ValidationParameters!, - theoryData.CallContext, - CancellationToken.None); - - if (validationResult.IsValid) - { - context.AddDiff("validationResult.IsValid is true, expected false"); - } - else - { - ValidationError validationError = validationResult.UnwrapError(); - IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.IssuerSigningKeyValidationError, context); - theoryData.ExpectedException.ProcessException(validationError.GetException(), context); - } - } - catch (Exception ex) - { - theoryData.ExpectedException.ProcessException(ex, context); - } - - TestUtilities.AssertFailIfErrors(context); - } - - public static TheoryData IssuerSigningKey_ExtensibilityTestCases + [Theory, MemberData( + nameof(GenerateIssuerSigningKeyExtensibilityTestCases), + parameters: ["SAML", 1], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility( + IssuerSigningKeyExtensibilityTheoryData theoryData) { - get - { - var theoryData = new TheoryData(); - CallContext callContext = new CallContext(); - var utcNow = DateTime.UtcNow; - var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); - - #region return CustomIssuerSigningKeyValidationError - // Test cases where delegate is overridden and return a CustomIssuerSigningKeyValidationError - // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "CustomIssuerSigningKeyValidatorDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSigningKeyException), - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate)), - IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(SecurityTokenInvalidSigningKeyException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 160), - null) - }); - - // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerSigningKeyException : SecurityTokenInvalidIssuerSigningKeyException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "CustomIssuerSigningKeyValidatorCustomExceptionDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidSigningKeyException), - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate)), - IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(CustomSecurityTokenInvalidSigningKeyException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 175), - null) - }); - - // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: NotSupportedException : SystemException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "CustomIssuerSigningKeyValidatorUnknownExceptionDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate, - extraStackFrames: 1) - { - // CustomIssuerSigningKeyValidationError 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(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate))), - IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorUnknownExceptionDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(NotSupportedException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 205), - null) - }); - - // CustomIssuerSigningKeyValidationError : IssuerSigningKeyValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidSigningKeyException), - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate)), - IssuerSigningKeyValidationError = new CustomIssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.CustomIssuerSigningKeyValidatorCustomExceptionCustomFailureTypeDelegate), null), - CustomIssuerSigningKeyValidationError.CustomIssuerSigningKeyValidationFailureType, - typeof(CustomSecurityTokenInvalidSigningKeyException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 190), - null), - }); - #endregion - - #region return IssuerSigningKeyValidationError - // Test cases where delegate is overridden and return an IssuerSigningKeyValidationError - // IssuerSigningKeyValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "IssuerSigningKeyValidatorDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSigningKeyException), - nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate)), - IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(SecurityTokenInvalidSigningKeyException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 235), - null) - }); - - // IssuerSigningKeyValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidIssuerSigningKeyException : SecurityTokenInvalidIssuerSigningKeyException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate, - extraStackFrames: 1) - { - // IssuerSigningKeyValidationError does not handle the exception type 'CustomSecurityTokenInvalidIssuerSigningKeyException' - ExpectedException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenInvalidSigningKeyException), - nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate))), - IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomIssuerSigningKeyExceptionTypeDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(CustomSecurityTokenInvalidSigningKeyException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 259), - null) - }); - - // IssuerSigningKeyValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "IssuerSigningKeyValidatorCustomExceptionTypeDelegate", - utcNow, - CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate, - extraStackFrames: 1) - { - // IssuerSigningKeyValidationError 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(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate))), - IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( - new MessageDetail( - nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorCustomExceptionTypeDelegate), null), - ValidationFailureType.SigningKeyValidationFailed, - typeof(CustomSecurityTokenException), - new StackFrame("CustomIssuerSigningKeyValidationDelegates.cs", 274), - null) - }); - - // IssuerSigningKeyValidationError : ValidationError, ExceptionType: SecurityTokenInvalidIssuerSigningKeyException, inner: CustomSecurityTokenInvalidIssuerSigningKeyException - theoryData.Add(new IssuerSigningKeyExtensibilityTheoryData( - "IssuerSigningKeyValidatorThrows", - utcNow, - CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorThrows, - extraStackFrames: 0) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSigningKeyException), - string.Format(Tokens.LogMessages.IDX10274), - typeof(CustomSecurityTokenInvalidSigningKeyException)), - IssuerSigningKeyValidationError = new IssuerSigningKeyValidationError( - new MessageDetail( - string.Format(Tokens.LogMessages.IDX10274), null), - ValidationFailureType.IssuerSigningKeyValidatorThrew, - typeof(SecurityTokenInvalidSigningKeyException), - new StackFrame("SamlSecurityTokenHandler.ValidateToken.Internal.cs", 250), - null, - new SecurityTokenInvalidSigningKeyException(nameof(CustomIssuerSigningKeyValidationDelegates.IssuerSigningKeyValidatorThrows)) - ) - }); - #endregion - - return theoryData; - } + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_IssuerSigningKeyValidator_Extensibility)); } - public class IssuerSigningKeyExtensibilityTheoryData : TheoryDataBase + public static TheoryData GenerateIssuerSigningKeyExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - internal IssuerSigningKeyExtensibilityTheoryData(string testId, DateTime utcNow, IssuerSigningKeyValidationDelegate issuerSigningKeyValidator, int extraStackFrames) : base(testId) - { - SamlToken = (SamlSecurityToken)SamlSecurityTokenHandler.CreateToken( - new SecurityTokenDescriptor() - { - Subject = Default.SamlClaimsIdentity, - Issuer = Default.Issuer, - }); - - ValidationParameters = new ValidationParameters - { - AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, - AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, - IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, - IssuerSigningKeyValidator = issuerSigningKeyValidator, - LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, - SignatureValidator = (SecurityToken token, ValidationParameters validationParameters, BaseConfiguration? configuration, CallContext callContext) => - { - token.SigningKey = SigningKey; - - return SigningKey; - }, - TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, - TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation - }; - - ExtraStackFrames = extraStackFrames; - } - - public SamlSecurityToken SamlToken { get; } - - public SamlSecurityTokenHandler SamlSecurityTokenHandler { get; } = new SamlSecurityTokenHandler(); - - public bool IsValid { get; set; } - - public SecurityKey SigningKey { get; set; } = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key; - - internal ValidationParameters? ValidationParameters { get; set; } - - internal IssuerSigningKeyValidationError? IssuerSigningKeyValidationError { get; set; } - - internal int ExtraStackFrames { get; } + return ExtensibilityTesting.GenerateIssuerSigningKeyExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "SamlSecurityTokenHandler.ValidateToken.Internal.cs"); } } } diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Signature.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Signature.cs index 467bce3c42..85f57bb942 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Signature.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Signature.cs @@ -1,13 +1,8 @@ // 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.Logging; -using Microsoft.IdentityModel.TestUtils; - +using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -15,255 +10,27 @@ namespace Microsoft.IdentityModel.Tokens.Saml.Extensibility.Tests { public partial class SamlSecurityTokenHandlerValidateTokenAsyncTests { - [Theory, MemberData(nameof(Signature_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateTokenAsync_SignatureValidator_Extensibility(SignatureExtensibilityTheoryData theoryData) - { - var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_SignatureValidator_Extensibility)}", theoryData); - context.IgnoreType = false; - for (int i = 0; i < theoryData.ExtraStackFrames; i++) - theoryData.SignatureValidationError!.AddStackFrame(new StackFrame(false)); - - try - { - ValidationResult validationResult = await theoryData.SamlSecurityTokenHandler.ValidateTokenAsync( - theoryData.SamlToken!, - theoryData.ValidationParameters!, - theoryData.CallContext, - CancellationToken.None); - - if (validationResult.IsValid) - { - // We expect the validation to fail, but it passed - context.AddDiff("ValidationResult is Valid."); - } - else - { - ValidationError validationError = validationResult.UnwrapError(); - IdentityComparer.AreValidationErrorsEqual(validationError, theoryData.SignatureValidationError, context); - theoryData.ExpectedException.ProcessException(validationError.GetException(), context); - } - } - catch (Exception ex) - { - theoryData.ExpectedException.ProcessException(ex, context); - } - - TestUtilities.AssertFailIfErrors(context); - } - - public static TheoryData Signature_ExtensibilityTestCases + [Theory, MemberData( + nameof(GenerateSignatureExtensibilityTestCases), + parameters: ["SAML", 2], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_SignatureValidator_Extensibility( + SignatureExtensibilityTheoryData theoryData) { - get - { - var theoryData = new TheoryData(); - CallContext callContext = new CallContext(); - var utcNow = DateTime.UtcNow; - var utcPlusOneHour = utcNow + TimeSpan.FromHours(1); - - #region return CustomSignatureValidationError - // Test cases where delegate is overridden and return a CustomSignatureValidationError - // CustomSignatureValidationError : SignatureValidationError, ExceptionType: SecurityTokenInvalidSignatureException - theoryData.Add(new SignatureExtensibilityTheoryData( - "CustomSignatureValidatorDelegate", - utcNow, - CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate)), - SignatureValidationError = new CustomSignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(SecurityTokenInvalidSignatureException), - new StackFrame("CustomSignatureValidationDelegates.cs", 160)) - }); - - // CustomSignatureValidationError : SignatureValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException - theoryData.Add(new SignatureExtensibilityTheoryData( - "CustomSignatureValidatorCustomExceptionDelegate", - utcNow, - CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidSignatureException), - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate)), - SignatureValidationError = new CustomSignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(CustomSecurityTokenInvalidSignatureException), - new StackFrame("CustomSignatureValidationDelegates.cs", 175)), - }); - - // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException - theoryData.Add(new SignatureExtensibilityTheoryData( - "CustomSignatureValidatorUnknownExceptionDelegate", - utcNow, - CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate, - extraStackFrames: 2) - { - // CustomSignatureValidationError 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(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate))), - SignatureValidationError = new CustomSignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorUnknownExceptionDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(NotSupportedException), - new StackFrame("CustomSignatureValidationDelegates.cs", 205)), - }); - - // CustomSignatureValidationError : SignatureValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType - theoryData.Add(new SignatureExtensibilityTheoryData( - "CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate", - utcNow, - CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenInvalidSignatureException), - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate)), - SignatureValidationError = new CustomSignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.CustomSignatureValidatorCustomExceptionCustomFailureTypeDelegate), null), - CustomSignatureValidationError.CustomSignatureValidationFailureType, - typeof(CustomSecurityTokenInvalidSignatureException), - new StackFrame("CustomSignatureValidationDelegates.cs", 190)), - }); - #endregion - - #region return SignatureValidationError - // Test cases where delegate is overridden and return an SignatureValidationError - // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException - theoryData.Add(new SignatureExtensibilityTheoryData( - "SignatureValidatorDelegate", - utcNow, - CustomSignatureValidationDelegates.SignatureValidatorDelegate, - extraStackFrames: 2) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate)), - SignatureValidationError = new SignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.SignatureValidatorDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(SecurityTokenInvalidSignatureException), - new StackFrame("CustomSignatureValidationDelegates.cs", 235)) - }); - - // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidSignatureException : SecurityTokenInvalidSignatureException - theoryData.Add(new SignatureExtensibilityTheoryData( - "SignatureValidatorCustomSignatureExceptionTypeDelegate", - utcNow, - CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate, - extraStackFrames: 2) - { - // SignatureValidationError does not handle the exception type 'CustomSecurityTokenInvalidSignatureException' - ExpectedException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenInvalidSignatureException), - nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate))), - SignatureValidationError = new SignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomSignatureExceptionTypeDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(CustomSecurityTokenInvalidSignatureException), - new StackFrame("CustomSignatureValidationDelegates.cs", 259)) - }); - - // SignatureValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException - theoryData.Add(new SignatureExtensibilityTheoryData( - "SignatureValidatorCustomExceptionTypeDelegate", - utcNow, - CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate, - extraStackFrames: 2) - { - // SignatureValidationError 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(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate))), - SignatureValidationError = new SignatureValidationError( - new MessageDetail( - nameof(CustomSignatureValidationDelegates.SignatureValidatorCustomExceptionTypeDelegate), null), - ValidationFailureType.SignatureValidationFailed, - typeof(CustomSecurityTokenException), - new StackFrame("CustomSignatureValidationDelegates.cs", 274)) - }); - - // SignatureValidationError : ValidationError, ExceptionType: SecurityTokenInvalidSignatureException, inner: CustomSecurityTokenInvalidSignatureException - theoryData.Add(new SignatureExtensibilityTheoryData( - "SignatureValidatorThrows", - utcNow, - CustomSignatureValidationDelegates.SignatureValidatorThrows, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenInvalidSignatureException), - string.Format(Tokens.LogMessages.IDX10272), - typeof(CustomSecurityTokenInvalidSignatureException)), - SignatureValidationError = new SignatureValidationError( - new MessageDetail( - string.Format(Tokens.LogMessages.IDX10272), null), - ValidationFailureType.SignatureValidatorThrew, - typeof(SecurityTokenInvalidSignatureException), - new StackFrame("SamlSecurityTokenHandler.ValidateSignature.cs", 250), - null, // no inner validation error - new SecurityTokenInvalidSignatureException(nameof(CustomSignatureValidationDelegates.SignatureValidatorThrows)) - ) - }); - #endregion - - return theoryData; - } + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_SignatureValidator_Extensibility)); } - public class SignatureExtensibilityTheoryData : TheoryDataBase + public static TheoryData GenerateSignatureExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - internal SignatureExtensibilityTheoryData(string testId, DateTime utcNow, SignatureValidationDelegate signatureValidator, int extraStackFrames) : base(testId) - { - SamlToken = (SamlSecurityToken)SamlSecurityTokenHandler.CreateToken( - new SecurityTokenDescriptor() - { - Subject = Default.SamlClaimsIdentity, - Issuer = Default.Issuer, - }); - - ValidationParameters = new ValidationParameters - { - AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, - AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, - IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, - IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, - LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, - SignatureValidator = signatureValidator, - TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation, - TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation - }; - - ExtraStackFrames = extraStackFrames; - } - - public SamlSecurityToken SamlToken { get; } - - public SamlSecurityTokenHandler SamlSecurityTokenHandler { get; } = new SamlSecurityTokenHandler(); - - public bool IsValid { get; set; } - - internal ValidationParameters ValidationParameters { get; } - - internal SignatureValidationError? SignatureValidationError { get; set; } - - internal int ExtraStackFrames { get; } + return ExtensibilityTesting.GenerateSignatureExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "SamlSecurityTokenHandler.ValidateSignature.cs"); } } } diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.TokenReplay.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.TokenReplay.cs index c603ebcacd..ca52b2eb49 100644 --- a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.TokenReplay.cs +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.TokenReplay.cs @@ -1,13 +1,8 @@ // 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.Logging; -using Microsoft.IdentityModel.TestUtils; - +using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; using Xunit; #nullable enable @@ -15,264 +10,27 @@ namespace Microsoft.IdentityModel.Tokens.Saml.Extensibility.Tests { public partial class SamlSecurityTokenHandlerValidateTokenAsyncTests { - [Theory, MemberData(nameof(TokenReplay_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)] - public async Task ValidateTokenAsync_TokenReplayValidator_Extensibility(TokenReplayExtensibilityTheoryData theoryData) - { - var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_TokenReplayValidator_Extensibility)}", theoryData); - context.IgnoreType = false; - for (int i = 0; i < theoryData.ExtraStackFrames; i++) - theoryData.TokenReplayValidationError!.AddStackFrame(new StackFrame(false)); - - try - { - ValidationResult validationResult = await theoryData.SamlSecurityTokenHandler.ValidateTokenAsync( - theoryData.SamlToken!, - 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.TokenReplayValidationError, context); - theoryData.ExpectedException.ProcessException(validationError.GetException(), context); - } - } - catch (Exception ex) - { - theoryData.ExpectedException.ProcessException(ex, context); - } - - TestUtilities.AssertFailIfErrors(context); - } - - public static TheoryData TokenReplay_ExtensibilityTestCases + [Theory, MemberData( + nameof(GenerateTokenReplayExtensibilityTestCases), + parameters: ["SAML", 1], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_TokenReplayValidator_Extensibility( + TokenReplayExtensibilityTheoryData theoryData) { - get - { - var theoryData = new TheoryData(); - CallContext callContext = new CallContext(); - var utcNow = DateTime.UtcNow; - var expirationTime = utcNow + TimeSpan.FromHours(1); - - #region return CustomTokenReplayValidationError - // Test cases where delegate is overridden and return a CustomTokenReplayValidationError - // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: SecurityTokenReplayDetectedException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "CustomTokenReplayValidationDelegate", - utcNow, - CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenReplayDetectedException), - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate)), - TokenReplayValidationError = new CustomTokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidationDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(SecurityTokenReplayDetectedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 160), - expirationTime) - }); - - // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: CustomSecurityTokenReplayDetectedException : SecurityTokenReplayDetectedException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "CustomTokenReplayValidatorCustomExceptionDelegate", - utcNow, - CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenReplayDetectedException), - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate)), - TokenReplayValidationError = new CustomTokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(CustomSecurityTokenReplayDetectedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 175), - expirationTime), - }); - - // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: NotSupportedException : SystemException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "CustomTokenReplayValidatorUnknownExceptionDelegate", - utcNow, - CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate, - extraStackFrames: 1) - { - // CustomTokenReplayValidationError 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(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate))), - TokenReplayValidationError = new CustomTokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorUnknownExceptionDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(NotSupportedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 205), - expirationTime), - }); - - // CustomTokenReplayValidationError : TokenReplayValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate", - utcNow, - CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(CustomSecurityTokenReplayDetectedException), - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate)), - TokenReplayValidationError = new CustomTokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.CustomTokenReplayValidatorCustomExceptionCustomFailureTypeDelegate), null), - CustomTokenReplayValidationError.CustomTokenReplayValidationFailureType, - typeof(CustomSecurityTokenReplayDetectedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 190), - expirationTime), - }); - #endregion - - #region return TokenReplayValidationError - // Test cases where delegate is overridden and return an TokenReplayValidationError - // TokenReplayValidationError : ValidationError, ExceptionType: SecurityTokenReplayDetectedException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "TokenReplayValidationDelegate", - utcNow, - CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate, - extraStackFrames: 1) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenReplayDetectedException), - nameof(CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate)), - TokenReplayValidationError = new TokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.TokenReplayValidationDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(SecurityTokenReplayDetectedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 235), - expirationTime) - }); - - // TokenReplayValidationError : ValidationError, ExceptionType: CustomSecurityTokenReplayDetectedException : SecurityTokenReplayDetectedException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate", - utcNow, - CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate, - extraStackFrames: 1) - { - // TokenReplayValidationError does not handle the exception type 'CustomSecurityTokenReplayDetectedException' - ExpectedException = ExpectedException.SecurityTokenException( - LogHelper.FormatInvariant( - Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; - typeof(CustomSecurityTokenReplayDetectedException), - nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate))), - TokenReplayValidationError = new TokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomTokenReplayDetectedExceptionTypeDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(CustomSecurityTokenReplayDetectedException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 259), - expirationTime) - }); - - // TokenReplayValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "TokenReplayValidatorCustomExceptionTypeDelegate", - utcNow, - CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate, - extraStackFrames: 1) - { - // TokenReplayValidationError 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(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate))), - TokenReplayValidationError = new TokenReplayValidationError( - new MessageDetail( - nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorCustomExceptionTypeDelegate), null), - ValidationFailureType.TokenReplayValidationFailed, - typeof(CustomSecurityTokenException), - new StackFrame("CustomTokenReplayValidationDelegates.cs", 274), - expirationTime) - }); - - // TokenReplayValidationError : ValidationError, ExceptionType: SecurityTokenReplayDetectedException, inner: CustomSecurityTokenReplayDetectedException - theoryData.Add(new TokenReplayExtensibilityTheoryData( - "TokenReplayValidatorThrows", - utcNow, - CustomTokenReplayValidationDelegates.TokenReplayValidatorThrows, - extraStackFrames: 0) - { - ExpectedException = new ExpectedException( - typeof(SecurityTokenReplayDetectedException), - string.Format(Tokens.LogMessages.IDX10276), - typeof(CustomSecurityTokenReplayDetectedException)), - TokenReplayValidationError = new TokenReplayValidationError( - new MessageDetail( - string.Format(Tokens.LogMessages.IDX10276), null), - ValidationFailureType.TokenReplayValidatorThrew, - typeof(SecurityTokenReplayDetectedException), - new StackFrame("SamlSecurityTokenHandler.ValidateToken.Internal.cs", 250), - expirationTime, - new SecurityTokenReplayDetectedException(nameof(CustomTokenReplayValidationDelegates.TokenReplayValidatorThrows)) - ) - }); - #endregion - - return theoryData; - } + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_TokenReplayValidator_Extensibility)); } - public class TokenReplayExtensibilityTheoryData : TheoryDataBase + public static TheoryData GenerateTokenReplayExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) { - internal TokenReplayExtensibilityTheoryData(string testId, DateTime utcNow, TokenReplayValidationDelegate tokenReplayValidator, int extraStackFrames) : base(testId) - { - SamlToken = (SamlSecurityToken)SamlSecurityTokenHandler.CreateToken( - new SecurityTokenDescriptor() - { - Subject = Default.SamlClaimsIdentity, - Issuer = Default.Issuer, - IssuedAt = utcNow, - NotBefore = utcNow, - Expires = utcNow + TimeSpan.FromHours(1), - }); - - ValidationParameters = new ValidationParameters - { - AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation, - AudienceValidator = SkipValidationDelegates.SkipAudienceValidation, - IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation, - IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation, - LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation, - SignatureValidator = SkipValidationDelegates.SkipSignatureValidation, - TokenReplayValidator = tokenReplayValidator, - TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation - }; - - ExtraStackFrames = extraStackFrames; - } - - public SamlSecurityToken SamlToken { get; } - - public SamlSecurityTokenHandler SamlSecurityTokenHandler { get; } = new SamlSecurityTokenHandler(); - - public bool IsValid { get; set; } - - internal ValidationParameters? ValidationParameters { get; set; } - - internal TokenReplayValidationError? TokenReplayValidationError { get; set; } - - internal int ExtraStackFrames { get; } + return ExtensibilityTesting.GenerateTokenReplayExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "SamlSecurityTokenHandler.ValidateToken.Internal.cs"); } } } From 309e03665e125494cf8d81390934c8050e4abeb9 Mon Sep 17 00:00:00 2001 From: BrentSchmaltz Date: Tue, 3 Dec 2024 11:21:37 -0800 Subject: [PATCH 14/23] Stack parameters to improve reading of code. (#3031) Fix some spelling. Co-authored-by: id4s --- .../AsymmetricAdapter.cs | 51 +++- .../AsymmetricSignatureProvider.cs | 92 +++++-- .../CryptoProviderFactory.cs | 225 +++++++++++++----- .../SignatureProvider.cs | 4 +- .../SymmetricSignatureProvider.cs | 71 ++++-- 5 files changed, 337 insertions(+), 106 deletions(-) diff --git a/src/Microsoft.IdentityModel.Tokens/AsymmetricAdapter.cs b/src/Microsoft.IdentityModel.Tokens/AsymmetricAdapter.cs index 5bca1e9317..7b09c89324 100644 --- a/src/Microsoft.IdentityModel.Tokens/AsymmetricAdapter.cs +++ b/src/Microsoft.IdentityModel.Tokens/AsymmetricAdapter.cs @@ -47,14 +47,23 @@ internal AsymmetricAdapter(SecurityKey key, string algorithm, bool requirePrivat { } - internal AsymmetricAdapter(SecurityKey key, string algorithm, HashAlgorithm hashAlgorithm, HashAlgorithmName hashAlgorithmName, bool requirePrivateKey) + internal AsymmetricAdapter( + SecurityKey key, + string algorithm, + HashAlgorithm hashAlgorithm, + HashAlgorithmName hashAlgorithmName, + bool requirePrivateKey) : this(key, algorithm, hashAlgorithm, requirePrivateKey) { HashAlgorithmName = hashAlgorithmName; } - internal AsymmetricAdapter(SecurityKey key, string algorithm, HashAlgorithm hashAlgorithm, bool requirePrivateKey) + internal AsymmetricAdapter( + SecurityKey key, + string algorithm, + HashAlgorithm hashAlgorithm, + bool requirePrivateKey) { HashAlgorithm = hashAlgorithm; @@ -79,7 +88,11 @@ internal AsymmetricAdapter(SecurityKey key, string algorithm, HashAlgorithm hash else if (securityKey is ECDsaSecurityKey edcsaSecurityKeyFromJsonWebKey) InitializeUsingEcdsaSecurityKey(edcsaSecurityKeyFromJsonWebKey); else - throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10684, LogHelper.MarkAsNonPII(algorithm), key))); + throw LogHelper.LogExceptionMessage( + new NotSupportedException( + LogHelper.FormatInvariant( + LogMessages.IDX10684, + LogHelper.MarkAsNonPII(algorithm), key))); } } else if (key is ECDsaSecurityKey ecdsaKey) @@ -87,7 +100,11 @@ internal AsymmetricAdapter(SecurityKey key, string algorithm, HashAlgorithm hash InitializeUsingEcdsaSecurityKey(ecdsaKey); } else - throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10684, LogHelper.MarkAsNonPII(algorithm), key))); + throw LogHelper.LogExceptionMessage( + new NotSupportedException( + LogHelper.FormatInvariant( + LogMessages.IDX10684, + LogHelper.MarkAsNonPII(algorithm), key))); } internal byte[] Decrypt(byte[] data) @@ -233,7 +250,10 @@ private void InitializeUsingRsaSecurityKey(RsaSecurityKey rsaSecurityKey, string } } - private void InitializeUsingX509SecurityKey(X509SecurityKey x509SecurityKey, string algorithm, bool requirePrivateKey) + private void InitializeUsingX509SecurityKey( + X509SecurityKey x509SecurityKey, + string algorithm, + bool requirePrivateKey) { if (requirePrivateKey) InitializeUsingRsa(x509SecurityKey.PrivateKey as RSA, algorithm); @@ -249,7 +269,10 @@ internal byte[] Sign(byte[] bytes) } #if NET6_0_OR_GREATER - internal bool SignUsingSpan(ReadOnlySpan data, Span destination, out int bytesWritten) + internal bool SignUsingSpan( + ReadOnlySpan data, + Span destination, + out int bytesWritten) { return _signUsingSpanFunction(data, destination, out bytesWritten); } @@ -274,7 +297,10 @@ private static byte[] SignUsingOffsetNotFound(byte[] b, int c, int d) #if NET6_0_OR_GREATER #pragma warning disable CA1801 // Review unused parameters - private static bool SignUsingSpanNotFound(ReadOnlySpan data, Span destination, out int bytesWritten) + private static bool SignUsingSpanNotFound( + ReadOnlySpan data, + Span destination, + out int bytesWritten) #pragma warning restore CA1801 // Review unused parameters { // we should never get here, its a bug if we do. @@ -288,7 +314,10 @@ private byte[] SignECDsa(byte[] bytes) } #if NET6_0_OR_GREATER - internal bool SignUsingSpanECDsa(ReadOnlySpan data, Span destination, out int bytesWritten) + internal bool SignUsingSpanECDsa( + ReadOnlySpan data, + Span destination, + out int bytesWritten) { // ECDSA.TrySignData will return true and set bytesWritten = 64, if destination is null. if (destination.Length == 0) @@ -397,7 +426,11 @@ private bool VerifyUsingOffsetRsa(byte[] bytes, int offset, int count, byte[] si #if NET6_0_OR_GREATER return VerifyUsingSpan(isRSA: true, bytes.AsSpan(offset, count), signature); #else - return RSA.VerifyHash(HashAlgorithm.ComputeHash(bytes, offset, count), signature, HashAlgorithmName, RSASignaturePadding); + return RSA.VerifyHash( + HashAlgorithm.ComputeHash(bytes, offset, count), + signature, + HashAlgorithmName, + RSASignaturePadding); #endif } diff --git a/src/Microsoft.IdentityModel.Tokens/AsymmetricSignatureProvider.cs b/src/Microsoft.IdentityModel.Tokens/AsymmetricSignatureProvider.cs index b767713bb1..0117c21b5e 100644 --- a/src/Microsoft.IdentityModel.Tokens/AsymmetricSignatureProvider.cs +++ b/src/Microsoft.IdentityModel.Tokens/AsymmetricSignatureProvider.cs @@ -22,7 +22,7 @@ public class AsymmetricSignatureProvider : SignatureProvider /// /// Mapping from algorithm to minimum .KeySize when creating signatures. /// - public static readonly Dictionary DefaultMinimumAsymmetricKeySizeInBitsForSigningMap = new Dictionary() + public static readonly Dictionary DefaultMinimumAsymmetricKeySizeInBitsForSigningMap = new() { { SecurityAlgorithms.EcdsaSha256, 256 }, { SecurityAlgorithms.EcdsaSha384, 256 }, @@ -47,7 +47,7 @@ public class AsymmetricSignatureProvider : SignatureProvider /// /// Mapping from algorithm to minimum .KeySize when verifying signatures. /// - public static readonly Dictionary DefaultMinimumAsymmetricKeySizeInBitsForVerifyingMap = new Dictionary() + public static readonly Dictionary DefaultMinimumAsymmetricKeySizeInBitsForVerifyingMap = new() { { SecurityAlgorithms.EcdsaSha256, 256 }, { SecurityAlgorithms.EcdsaSha384, 256 }, @@ -69,13 +69,20 @@ public class AsymmetricSignatureProvider : SignatureProvider { SecurityAlgorithms.RsaSsaPssSha512Signature, 1040 } }; - internal AsymmetricSignatureProvider(SecurityKey key, string algorithm, CryptoProviderFactory cryptoProviderFactory) + internal AsymmetricSignatureProvider( + SecurityKey key, + string algorithm, + CryptoProviderFactory cryptoProviderFactory) : this(key, algorithm) { _cryptoProviderFactory = cryptoProviderFactory; } - internal AsymmetricSignatureProvider(SecurityKey key, string algorithm, bool willCreateSignatures, CryptoProviderFactory cryptoProviderFactory) + internal AsymmetricSignatureProvider( + SecurityKey key, + string algorithm, + bool willCreateSignatures, + CryptoProviderFactory cryptoProviderFactory) : this(key, algorithm, willCreateSignatures) { _cryptoProviderFactory = cryptoProviderFactory; @@ -104,7 +111,10 @@ public AsymmetricSignatureProvider(SecurityKey key, string algorithm) /// Thrown if is true and is less than the required size for signing. /// Thrown if is less than the required size for verifying signatures. /// Thrown if the runtime is unable to create a suitable cryptographic provider. - public AsymmetricSignatureProvider(SecurityKey key, string algorithm, bool willCreateSignatures) + public AsymmetricSignatureProvider( + SecurityKey key, + string algorithm, + bool willCreateSignatures) : base(key, algorithm) { _cryptoProviderFactory = key.CryptoProviderFactory; @@ -116,13 +126,21 @@ public AsymmetricSignatureProvider(SecurityKey key, string algorithm, bool willC JsonWebKeyConverter.TryConvertToSecurityKey(jsonWebKey, out SecurityKey _); if (willCreateSignatures && FoundPrivateKey(key) == PrivateKeyStatus.DoesNotExist) - throw LogHelper.LogExceptionMessage(new InvalidOperationException(LogHelper.FormatInvariant(LogMessages.IDX10638, key))); + throw LogHelper.LogExceptionMessage( + new InvalidOperationException( + LogHelper.FormatInvariant(LogMessages.IDX10638, key))); if (!_cryptoProviderFactory.IsSupportedAlgorithm(algorithm, key)) - throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10634, LogHelper.MarkAsNonPII((algorithm)), key))); + throw LogHelper.LogExceptionMessage( + new NotSupportedException( + LogHelper.FormatInvariant( + LogMessages.IDX10634, + LogHelper.MarkAsNonPII((algorithm)), key))); WillCreateSignatures = willCreateSignatures; - _asymmetricAdapterObjectPool = new DisposableObjectPool(CreateAsymmetricAdapter, _cryptoProviderFactory.SignatureProviderObjectPoolCacheSize); + _asymmetricAdapterObjectPool = new DisposableObjectPool( + CreateAsymmetricAdapter, + _cryptoProviderFactory.SignatureProviderObjectPoolCacheSize); } /// @@ -171,8 +189,13 @@ protected virtual HashAlgorithmName GetHashAlgorithmName(string algorithm) private AsymmetricAdapter CreateAsymmetricAdapter() { - var hashAlgoritmName = GetHashAlgorithmName(Algorithm); - return new AsymmetricAdapter(Key, Algorithm, _cryptoProviderFactory.CreateHashAlgorithm(hashAlgoritmName), hashAlgoritmName, WillCreateSignatures); + HashAlgorithmName hashAlgorithmName = GetHashAlgorithmName(Algorithm); + return new AsymmetricAdapter( + Key, + Algorithm, + _cryptoProviderFactory.CreateHashAlgorithm(hashAlgorithmName), + hashAlgorithmName, + WillCreateSignatures); } internal bool ValidKeySize() @@ -188,7 +211,10 @@ internal bool ValidKeySize() #if NET6_0_OR_GREATER /// - public override bool Sign(ReadOnlySpan input, Span signature, out int bytesWritten) + public override bool Sign( + ReadOnlySpan input, + Span signature, + out int bytesWritten) { if (input.Length == 0) throw LogHelper.LogArgumentNullException(nameof(input)); @@ -219,12 +245,14 @@ public override bool Sign(ReadOnlySpan input, Span signature, out in #endif /// - /// Produces a signature over the 'input' using the and algorithm passed to . + /// Produces a signature over the 'input' using the and algorithm passed + /// to . /// /// The bytes to be signed. /// A signature over the input. /// Thrown if is null or has length of 0. - /// Thrown If has been called. + /// Thrown if + /// has been called. /// Sign is thread safe. public override byte[] Sign(byte[] input) { @@ -317,23 +345,41 @@ public virtual void ValidateAsymmetricSecurityKeySize(SecurityKey key, string al if (convertedSecurityKey is AsymmetricSecurityKey convertedAsymmetricKey) keySize = convertedAsymmetricKey.KeySize; else if (convertedSecurityKey is SymmetricSecurityKey) - throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10704, key))); + throw LogHelper.LogExceptionMessage( + new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10704, key))); } else { - throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10704, key))); + throw LogHelper.LogExceptionMessage( + new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10704, key))); } if (willCreateSignatures) { if (MinimumAsymmetricKeySizeInBitsForSigningMap.ContainsKey(algorithm) && keySize < MinimumAsymmetricKeySizeInBitsForSigningMap[algorithm]) - throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(nameof(key), LogHelper.FormatInvariant(LogMessages.IDX10630, key, LogHelper.MarkAsNonPII(MinimumAsymmetricKeySizeInBitsForSigningMap[algorithm]), LogHelper.MarkAsNonPII(keySize)))); + throw LogHelper.LogExceptionMessage( + new ArgumentOutOfRangeException( + nameof(key), + LogHelper.FormatInvariant( + LogMessages.IDX10630, + key, + LogHelper.MarkAsNonPII( + MinimumAsymmetricKeySizeInBitsForSigningMap[algorithm]), + LogHelper.MarkAsNonPII(keySize)))); } else if (MinimumAsymmetricKeySizeInBitsForVerifyingMap.ContainsKey(algorithm) && keySize < MinimumAsymmetricKeySizeInBitsForVerifyingMap[algorithm]) { - throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(nameof(key), LogHelper.FormatInvariant(LogMessages.IDX10631, key, LogHelper.MarkAsNonPII(MinimumAsymmetricKeySizeInBitsForVerifyingMap[algorithm]), LogHelper.MarkAsNonPII(keySize)))); + throw LogHelper.LogExceptionMessage( + new ArgumentOutOfRangeException( + nameof(key), + LogHelper.FormatInvariant( + LogMessages.IDX10631, + key, + LogHelper.MarkAsNonPII( + MinimumAsymmetricKeySizeInBitsForVerifyingMap[algorithm]), + LogHelper.MarkAsNonPII(keySize)))); } } @@ -386,7 +432,13 @@ public override bool Verify(byte[] input, byte[] signature) } /// - public override bool Verify(byte[] input, int inputOffset, int inputLength, byte[] signature, int signatureOffset, int signatureLength) + public override bool Verify( + byte[] input, + int inputOffset, + int inputLength, + byte[] signature, + int signatureOffset, + int signatureLength) { if (input == null || input.Length == 0) throw LogHelper.LogArgumentNullException(nameof(input)); @@ -460,7 +512,7 @@ public override bool Verify(byte[] input, int inputOffset, int inputLength, byte } else { - // AsymetricAdapter.Verify could do this. + // AsymmetricAdapter.Verify could do this. // Having the logic here, handles EC and RSA. We can revisit when we start using spans in 3.1+. byte[] signatureBytes = new byte[signatureLength]; Array.Copy(signature, 0, signatureBytes, 0, signatureLength); @@ -490,7 +542,7 @@ protected override void Dispose(bool disposing) _disposed = true; if (disposing) { - foreach (var item in _asymmetricAdapterObjectPool.Items) + foreach (DisposableObjectPool.Element item in _asymmetricAdapterObjectPool.Items) item.Value?.Dispose(); CryptoProviderCache?.TryRemove(this); diff --git a/src/Microsoft.IdentityModel.Tokens/CryptoProviderFactory.cs b/src/Microsoft.IdentityModel.Tokens/CryptoProviderFactory.cs index bdbe960b47..850f2d5657 100644 --- a/src/Microsoft.IdentityModel.Tokens/CryptoProviderFactory.cs +++ b/src/Microsoft.IdentityModel.Tokens/CryptoProviderFactory.cs @@ -45,7 +45,14 @@ public static CryptoProviderFactory Default public static int DefaultSignatureProviderObjectPoolCacheSize { get => _defaultSignatureProviderObjectPoolCacheSize; - set => _defaultSignatureProviderObjectPoolCacheSize = value > 0 ? value : throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(nameof(value), LogHelper.FormatInvariant(LogMessages.IDX10698, LogHelper.MarkAsNonPII(value)))); + set => _defaultSignatureProviderObjectPoolCacheSize = value > 0 + ? value + : throw LogHelper.LogExceptionMessage( + new ArgumentOutOfRangeException( + nameof(value), + LogHelper.FormatInvariant( + LogMessages.IDX10698, + LogHelper.MarkAsNonPII(value)))); } /// @@ -96,8 +103,9 @@ public CryptoProviderFactory(CryptoProviderFactory other) /// /// Extensibility point for creating custom cryptographic operators. /// - /// By default, if set, will be called before creating cryptographic operators. - /// If true is returned, then will be called. The will throw if the + /// By default, if set, will be called before + /// creating cryptographic operators. If true is returned, then will be called. + /// The will throw if the /// Cryptographic operator returned is not of the correct type. public ICryptoProvider CustomCryptoProvider { get; set; } @@ -114,23 +122,33 @@ public int SignatureProviderObjectPoolCacheSize { get => _signatureProviderObjectPoolCacheSize; - set => _signatureProviderObjectPoolCacheSize = value > 0 ? value : throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(nameof(value), LogHelper.FormatInvariant(LogMessages.IDX10698, LogHelper.MarkAsNonPII(value)))); + set => _signatureProviderObjectPoolCacheSize = value > 0 + ? value + : throw LogHelper.LogExceptionMessage( + new ArgumentOutOfRangeException( + nameof(value), + LogHelper.FormatInvariant( + LogMessages.IDX10698, + LogHelper.MarkAsNonPII(value)))); } /// - /// Creates an instance of for a specific and . + /// Creates an instance of for a specific + /// and . /// /// The to use. /// The algorithm to use. /// Thrown if is null. /// Thrown if is null or empty. - /// Thrown if the combination of and is not supported. - /// Thrown if the type returned by is not assignable to . + /// Thrown if the combination of and + /// is not supported. + /// Thrown if the type returned by + /// is not assignable to . /// - /// If is set and returns true, - /// is called to obtain the . + /// If is set and + /// returns true, is called to obtain the . /// - /// Once done with the , call . + /// When you are finished with the , call . /// /// An instance of . public virtual AuthenticatedEncryptionProvider CreateAuthenticatedEncryptionProvider(SecurityKey key, string algorithm) @@ -145,7 +163,13 @@ public virtual AuthenticatedEncryptionProvider CreateAuthenticatedEncryptionProv { var cryptoProvider = CustomCryptoProvider.Create(algorithm, key) as AuthenticatedEncryptionProvider; if (cryptoProvider == null) - throw LogHelper.LogExceptionMessage(new InvalidOperationException(LogHelper.FormatInvariant(LogMessages.IDX10646, LogHelper.MarkAsNonPII(algorithm), key, LogHelper.MarkAsNonPII(typeof(AuthenticatedEncryptionProvider))))); + throw LogHelper.LogExceptionMessage( + new InvalidOperationException( + LogHelper.FormatInvariant( + LogMessages.IDX10646, + LogHelper.MarkAsNonPII(algorithm), + key, + LogHelper.MarkAsNonPII(typeof(AuthenticatedEncryptionProvider))))); return cryptoProvider; } @@ -153,7 +177,12 @@ public virtual AuthenticatedEncryptionProvider CreateAuthenticatedEncryptionProv if (SupportedAlgorithms.IsSupportedEncryptionAlgorithm(algorithm, key)) return new AuthenticatedEncryptionProvider(key, algorithm); - throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(LogMessages.IDX10652, LogHelper.MarkAsNonPII(algorithm)), nameof(algorithm))); + throw LogHelper.LogExceptionMessage( + new ArgumentException( + LogHelper.FormatInvariant( + LogMessages.IDX10652, + LogHelper.MarkAsNonPII(algorithm)), + nameof(algorithm))); } /// @@ -163,13 +192,15 @@ public virtual AuthenticatedEncryptionProvider CreateAuthenticatedEncryptionProv /// The algorithm to use. /// Thrown if is null. /// Thrown if is null or empty. - /// Thrown if the combination of and is not supported. - /// Thrown if the type returned by is not assignable to . + /// Thrown if the combination of and + /// is not supported. + /// Thrown if the type returned by + /// is not assignable to . /// - /// If is set and returns true, - /// is called to obtain the . + /// If is set and + /// returns true, is called to obtain the . /// - /// Once done with the , call . + /// When you are finished with the , call . /// /// An instance of . public virtual KeyWrapProvider CreateKeyWrapProvider(SecurityKey key, string algorithm) @@ -184,13 +215,15 @@ public virtual KeyWrapProvider CreateKeyWrapProvider(SecurityKey key, string alg /// The algorithm to use. /// Thrown if is null. /// Thrown if is null or empty. - /// Thrown if the combination of and is not supported. - /// Thrown if the type returned by is not assignable to . + /// Thrown if the combination of and + /// is not supported. + /// Thrown if the type returned by + /// is not assignable to . /// - /// If is set and returns true, - /// is called to obtain the . + /// If is set and + /// returns true, is called to obtain the . /// - /// Once done with the , call . + /// When you are finished with the , call . /// /// An instance of . public virtual KeyWrapProvider CreateKeyWrapProviderForUnwrap(SecurityKey key, string algorithm) @@ -209,7 +242,13 @@ private KeyWrapProvider CreateKeyWrapProvider(SecurityKey key, string algorithm, if (CustomCryptoProvider != null && CustomCryptoProvider.IsSupportedAlgorithm(algorithm, key, willUnwrap)) { if (!(CustomCryptoProvider.Create(algorithm, key, willUnwrap) is KeyWrapProvider keyWrapProvider)) - throw LogHelper.LogExceptionMessage(new InvalidOperationException(LogHelper.FormatInvariant(LogMessages.IDX10646, LogHelper.MarkAsNonPII(algorithm), key, LogHelper.MarkAsNonPII(typeof(SignatureProvider))))); + throw LogHelper.LogExceptionMessage( + new InvalidOperationException( + LogHelper.FormatInvariant( + LogMessages.IDX10646, + LogHelper.MarkAsNonPII(algorithm), + key, + LogHelper.MarkAsNonPII(typeof(SignatureProvider))))); return keyWrapProvider; } @@ -220,7 +259,12 @@ private KeyWrapProvider CreateKeyWrapProvider(SecurityKey key, string algorithm, if (SupportedAlgorithms.IsSupportedSymmetricKeyWrap(algorithm, key)) return new SymmetricKeyWrapProvider(key, algorithm); - throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10661, LogHelper.MarkAsNonPII(algorithm), key))); + throw LogHelper.LogExceptionMessage( + new NotSupportedException( + LogHelper.FormatInvariant( + LogMessages.IDX10661, + LogHelper.MarkAsNonPII(algorithm), + key))); } /// @@ -231,14 +275,16 @@ private KeyWrapProvider CreateKeyWrapProvider(SecurityKey key, string algorithm, /// Thrown if is null. /// Thrown if is null or empty. /// Thrown if is too small. - /// Thrown if is not assignable from or . + /// Thrown if is not assignable from + /// or . /// Thrown if the key or algorithm combination is not supported. - /// Thrown if the type returned by is not assignable to . + /// Thrown if the type returned by + /// is not assignable to . /// /// AsymmetricSignatureProviders require access to a PrivateKey for signing. - /// Once done with the , call . - /// If is set and returns true, - /// is called to obtain the . + /// When you are finished with the , call . + /// If is set and + /// returns true, is called to obtain the . /// /// /// A instance that can be used to create a signature. @@ -256,12 +302,15 @@ public virtual SignatureProvider CreateForSigning(SecurityKey key, string algori /// Thrown if is null. /// Thrown if is null or empty. /// Thrown if is too small. - /// Thrown if is not assignable from or . - /// Thrown if the combination of and is not supported. - /// Thrown if the type returned by is not assignable to . + /// Thrown if is not assignable from + /// or . + /// Thrown if the combination of and + /// is not supported. + /// Thrown if the type returned by + /// is not assignable to . /// /// AsymmetricSignatureProviders require access to a PrivateKey for signing. - /// Once done with the , call . + /// When you are finished with the , call . /// If is set and returns true, /// is called to obtain the . /// @@ -280,16 +329,19 @@ public virtual SignatureProvider CreateForSigning(SecurityKey key, string algori /// Thrown if is null. /// Thrown if is null or empty. /// Thrown if is too small. - /// Thrown if is not assignable from or . + /// Thrown if is not assignable from + /// or . /// Thrown if the combination of and is not supported. - /// Thrown if the type returned by is not assignable to . + /// Thrown if the type returned by + /// is not assignable to . /// - /// Once done with the , call . + /// When you are finished with the , call . /// If is set and returns true, /// is called to obtain the . /// /// - /// A instance that can be used to validate signatures using the and algorithm. + /// A instance that can be used to validate signatures using the + /// and algorithm. public virtual SignatureProvider CreateForVerifying(SecurityKey key, string algorithm) { return CreateForVerifying(key, algorithm, CacheSignatureProviders); @@ -304,11 +356,13 @@ public virtual SignatureProvider CreateForVerifying(SecurityKey key, string algo /// Thrown if is null. /// Thrown if is null or empty. /// Thrown if is too small. - /// Thrown if is not assignable from or . + /// Thrown if is not assignable from + /// or . /// Thrown if the combination of and is not supported. - /// Thrown if the type returned by is not assignable to . + /// Thrown if the type returned by + /// is not assignable to . /// - /// Once done with the , call . + /// When you are finished with the , call . /// If is set and returns true, /// is called to obtain the . /// @@ -324,10 +378,11 @@ public virtual SignatureProvider CreateForVerifying(SecurityKey key, string algo /// /// The name of the hash algorithm to create. /// Thrown if is null or empty. - /// Thrown if returns a type that is not assignable to . + /// Thrown if + /// returns a type that is not assignable to . /// Thrown if is not supported. /// - /// Once done with the , call . + /// When you are finished with the , call . /// If is set and returns true, /// is called to obtain the . /// @@ -338,7 +393,12 @@ public virtual HashAlgorithm CreateHashAlgorithm(HashAlgorithmName algorithm) if (CustomCryptoProvider != null && CustomCryptoProvider.IsSupportedAlgorithm(algorithm.Name)) { if (!(CustomCryptoProvider.Create(algorithm.Name) is HashAlgorithm hashAlgorithm)) - throw LogHelper.LogExceptionMessage(new InvalidOperationException(LogHelper.FormatInvariant(LogMessages.IDX10647, LogHelper.MarkAsNonPII(algorithm), LogHelper.MarkAsNonPII(typeof(HashAlgorithm))))); + throw LogHelper.LogExceptionMessage( + new InvalidOperationException( + LogHelper.FormatInvariant( + LogMessages.IDX10647, + LogHelper.MarkAsNonPII(algorithm), + LogHelper.MarkAsNonPII(typeof(HashAlgorithm))))); _typeToAlgorithmMap[hashAlgorithm.GetType().ToString()] = algorithm.Name; return hashAlgorithm; @@ -353,7 +413,11 @@ public virtual HashAlgorithm CreateHashAlgorithm(HashAlgorithmName algorithm) if (algorithm == HashAlgorithmName.SHA512) return SHA512.Create(); - throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10640, LogHelper.MarkAsNonPII(algorithm)))); + throw LogHelper.LogExceptionMessage( + new NotSupportedException( + LogHelper.FormatInvariant( + LogMessages.IDX10640, + LogHelper.MarkAsNonPII(algorithm)))); } /// @@ -361,10 +425,11 @@ public virtual HashAlgorithm CreateHashAlgorithm(HashAlgorithmName algorithm) /// /// The name of the hash algorithm to create. /// Thrown if is null or empty. - /// Thrown if returns a type that is not assignable to . + /// Thrown if returns a type that + /// is not assignable to . /// Thrown if is not supported. /// - /// Once done with the , call . + /// When you are finished with the , call . /// If is set and returns true, /// is called to obtain the . /// @@ -378,7 +443,12 @@ public virtual HashAlgorithm CreateHashAlgorithm(string algorithm) if (CustomCryptoProvider != null && CustomCryptoProvider.IsSupportedAlgorithm(algorithm)) { if (!(CustomCryptoProvider.Create(algorithm) is HashAlgorithm hashAlgorithm)) - throw LogHelper.LogExceptionMessage(new InvalidOperationException(LogHelper.FormatInvariant(LogMessages.IDX10647, LogHelper.MarkAsNonPII(algorithm), LogHelper.MarkAsNonPII(typeof(HashAlgorithm))))); + throw LogHelper.LogExceptionMessage( + new InvalidOperationException( + LogHelper.FormatInvariant( + LogMessages.IDX10647, + LogHelper.MarkAsNonPII(algorithm), + LogHelper.MarkAsNonPII(typeof(HashAlgorithm))))); _typeToAlgorithmMap[hashAlgorithm.GetType().ToString()] = algorithm; @@ -400,7 +470,11 @@ public virtual HashAlgorithm CreateHashAlgorithm(string algorithm) return SHA512.Create(); } - throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10640, LogHelper.MarkAsNonPII(algorithm)))); + throw LogHelper.LogExceptionMessage( + new NotSupportedException( + LogHelper.FormatInvariant( + LogMessages.IDX10640, + LogHelper.MarkAsNonPII(algorithm)))); } /// @@ -410,10 +484,11 @@ public virtual HashAlgorithm CreateHashAlgorithm(string algorithm) /// The name of the keyed hash algorithm to create. /// Thrown if is null. /// Thrown if is null or empty. - /// Thrown if returns a type that is not assignable to . + /// Thrown if returns a type that + /// is not assignable to . /// Thrown if is not supported. /// - /// Once done with the , call . + /// When you are finished with the , call . /// If is set and returns true, /// is called to obtain the . /// @@ -485,11 +560,18 @@ public virtual KeyedHashAlgorithm CreateKeyedHashAlgorithm(byte[] keyBytes, stri } default: - throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10666, LogHelper.MarkAsNonPII(algorithm)))); + throw LogHelper.LogExceptionMessage( + new NotSupportedException( + LogHelper.FormatInvariant( + LogMessages.IDX10666, + LogHelper.MarkAsNonPII(algorithm)))); } } - private static void ValidateKeySize(byte[] keyBytes, string algorithm, int expectedNumberOfBytes) + private static void ValidateKeySize( + byte[] keyBytes, + string algorithm, + int expectedNumberOfBytes) { if (keyBytes.Length < expectedNumberOfBytes) throw LogHelper.LogExceptionMessage( @@ -501,7 +583,11 @@ private static void ValidateKeySize(byte[] keyBytes, string algorithm, int expec LogHelper.MarkAsNonPII(keyBytes.Length * 8)))); } - private SignatureProvider CreateSignatureProvider(SecurityKey key, string algorithm, bool willCreateSignatures, bool cacheProvider) + private SignatureProvider CreateSignatureProvider( + SecurityKey key, + string algorithm, + bool willCreateSignatures, + bool cacheProvider) { if (key == null) throw LogHelper.LogArgumentNullException(nameof(key)); @@ -562,7 +648,13 @@ private SignatureProvider CreateSignatureProvider(SecurityKey key, string algori } catch (Exception ex) { - throw LogHelper.LogExceptionMessage(new InvalidOperationException(LogHelper.FormatInvariant(LogMessages.IDX10694, key, ex), ex)); + throw LogHelper.LogExceptionMessage( + new InvalidOperationException( + LogHelper.FormatInvariant( + LogMessages.IDX10694, + key, + ex), + ex)); } } else if (key is SymmetricSecurityKey) @@ -584,14 +676,24 @@ private SignatureProvider CreateSignatureProvider(SecurityKey key, string algori if (CacheSignatureProviders && cacheProvider) { - if (CryptoProviderCache.TryGetSignatureProvider(key, algorithm, typeofSignatureProvider, willCreateSignatures, out signatureProvider)) + if (CryptoProviderCache.TryGetSignatureProvider( + key, + algorithm, + typeofSignatureProvider, + willCreateSignatures, + out signatureProvider)) { signatureProvider.AddRef(); return signatureProvider; } if (!IsSupportedAlgorithm(algorithm, key)) - throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10634, LogHelper.MarkAsNonPII(algorithm), key))); + throw LogHelper.LogExceptionMessage( + new NotSupportedException( + LogHelper.FormatInvariant( + LogMessages.IDX10634, + LogHelper.MarkAsNonPII(algorithm), + key))); if (createAsymmetric) signatureProvider = new AsymmetricSignatureProvider(key, algorithm, willCreateSignatures, this); @@ -611,7 +713,12 @@ private SignatureProvider CreateSignatureProvider(SecurityKey key, string algori else { if (!IsSupportedAlgorithm(algorithm, key)) - throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10634, LogHelper.MarkAsNonPII(algorithm), key))); + throw LogHelper.LogExceptionMessage( + new NotSupportedException( + LogHelper.FormatInvariant( + LogMessages.IDX10634, + LogHelper.MarkAsNonPII(algorithm), + key))); if (createAsymmetric) { @@ -701,7 +808,9 @@ public virtual void ReleaseHashAlgorithm(HashAlgorithm hashAlgorithm) { if (hashAlgorithm == null) throw LogHelper.LogArgumentNullException(nameof(hashAlgorithm)); - else if (CustomCryptoProvider != null && _typeToAlgorithmMap.TryGetValue(hashAlgorithm.GetType().ToString(), out var algorithm) && CustomCryptoProvider.IsSupportedAlgorithm(algorithm)) + else if (CustomCryptoProvider != null + && _typeToAlgorithmMap.TryGetValue(hashAlgorithm.GetType().ToString(), out string algorithm) + && CustomCryptoProvider.IsSupportedAlgorithm(algorithm)) CustomCryptoProvider.Release(hashAlgorithm); else hashAlgorithm.Dispose(); diff --git a/src/Microsoft.IdentityModel.Tokens/SignatureProvider.cs b/src/Microsoft.IdentityModel.Tokens/SignatureProvider.cs index 4f011c9c97..f6423c4870 100644 --- a/src/Microsoft.IdentityModel.Tokens/SignatureProvider.cs +++ b/src/Microsoft.IdentityModel.Tokens/SignatureProvider.cs @@ -147,11 +147,11 @@ public virtual bool Sign(ReadOnlySpan data, Span destination, out in /// Verifies that a signature created over the 'input' matches the signature. Using and 'algorithm' passed to . /// /// The bytes to verify. - /// offset in to input bytes to caculate hash. + /// offset in to input bytes to calculate hash. /// number of bytes of signature to use. /// signature to compare against. /// offset into signature array. - /// how many bytes to verfiy. + /// how many bytes to verify. /// true if computed signature matches the signature parameter, false otherwise. /// 'input' is null. /// 'signature' is null. diff --git a/src/Microsoft.IdentityModel.Tokens/SymmetricSignatureProvider.cs b/src/Microsoft.IdentityModel.Tokens/SymmetricSignatureProvider.cs index bcc4f4a198..72f12a05a8 100644 --- a/src/Microsoft.IdentityModel.Tokens/SymmetricSignatureProvider.cs +++ b/src/Microsoft.IdentityModel.Tokens/SymmetricSignatureProvider.cs @@ -73,13 +73,29 @@ public SymmetricSignatureProvider(SecurityKey key, string algorithm, bool willCr : base(key, algorithm) { if (!key.CryptoProviderFactory.IsSupportedAlgorithm(algorithm, key)) - throw LogHelper.LogExceptionMessage(new NotSupportedException(LogHelper.FormatInvariant(LogMessages.IDX10634, LogHelper.MarkAsNonPII((algorithm)), key))); + throw LogHelper.LogExceptionMessage( + new NotSupportedException( + LogHelper.FormatInvariant( + LogMessages.IDX10634, + LogHelper.MarkAsNonPII((algorithm)), key))); if (key.KeySize < MinimumSymmetricKeySizeInBits) - throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(nameof(key), LogHelper.FormatInvariant(LogMessages.IDX10653, LogHelper.MarkAsNonPII((algorithm)), LogHelper.MarkAsNonPII(MinimumSymmetricKeySizeInBits), key, LogHelper.MarkAsNonPII(key.KeySize)))); + throw LogHelper.LogExceptionMessage( + new ArgumentOutOfRangeException( + nameof(key), + LogHelper.FormatInvariant( + LogMessages.IDX10653, + LogHelper.MarkAsNonPII( + (algorithm)), + LogHelper.MarkAsNonPII( + MinimumSymmetricKeySizeInBits), + key, + LogHelper.MarkAsNonPII(key.KeySize)))); WillCreateSignatures = willCreateSignatures; - _keyedHashObjectPool = new DisposableObjectPool(CreateKeyedHashAlgorithm, key.CryptoProviderFactory.SignatureProviderObjectPoolCacheSize); + _keyedHashObjectPool = new DisposableObjectPool( + CreateKeyedHashAlgorithm, + key.CryptoProviderFactory.SignatureProviderObjectPoolCacheSize); } /// @@ -95,7 +111,12 @@ public int MinimumSymmetricKeySizeInBits set { if (value < DefaultMinimumSymmetricKeySizeInBits) - throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(nameof(value), LogHelper.FormatInvariant(LogMessages.IDX10628, LogHelper.MarkAsNonPII(DefaultMinimumSymmetricKeySizeInBits)))); + throw LogHelper.LogExceptionMessage( + new ArgumentOutOfRangeException( + nameof(value), + LogHelper.FormatInvariant( + LogMessages.IDX10628, + LogHelper.MarkAsNonPII(DefaultMinimumSymmetricKeySizeInBits)))); _minimumSymmetricKeySizeInBits = value; } @@ -130,7 +151,7 @@ protected virtual byte[] GetKeyBytes(SecurityKey key) /// Returns a . /// This method is called just before a cryptographic operation. /// This provides the opportunity to obtain the from an object pool. - /// If this method is overridden, it is importont to override + /// If this method is overridden, it is important to override /// if custom releasing of the is desired. /// /// The hash algorithm to use to create the hash value. @@ -164,14 +185,16 @@ protected virtual void ReleaseKeyedHashAlgorithm(KeyedHashAlgorithm keyedHashAlg } /// - /// Produces a signature over the 'input' using the and 'algorithm' passed to . + /// Produces a signature over the 'input' using the and 'algorithm' + /// passed to . /// /// The bytes to sign. /// Signed bytes /// 'input' is null. /// 'input.Length' == 0. /// has been called. - /// is null. This can occur if a derived type deletes it or does not create it. + /// is null. + /// This can occur if a derived type deletes it or does not create it. /// Sign is thread safe. public override byte[] Sign(byte[] input) { @@ -268,7 +291,8 @@ public override byte[] Sign(byte[] input, int offset, int count) } /// - /// Verifies that a signature created over the 'input' matches the signature. Using and 'algorithm' passed to . + /// Verifies that a signature created over the 'input' matches the signature. Using and 'algorithm' + /// passed to . /// /// The bytes to verify. /// signature to compare against. @@ -278,7 +302,8 @@ public override byte[] Sign(byte[] input, int offset, int count) /// 'input.Length' == 0. /// 'signature.Length' == 0. /// has been called. - /// If the internal is null. This can occur if a derived type deletes it or does not create it. + /// If the internal is null. + /// This can occur if a derived type deletes it or does not create it. /// Verify is thread safe. public override bool Verify(byte[] input, byte[] signature) { @@ -315,7 +340,8 @@ public override bool Verify(byte[] input, byte[] signature) } /// - /// Verifies that a signature created over the 'input' matches the signature. Using and 'algorithm' passed to . + /// Verifies that a signature created over the 'input' matches the signature. Using and 'algorithm' + /// passed to . /// /// The bytes to verify. /// signature to compare against. @@ -327,7 +353,8 @@ public override bool Verify(byte[] input, byte[] signature) /// 'signature.Length' == 0. /// 'length < 1' /// has been called. - /// If the internal is null. This can occur if a derived type deletes it or does not create it. + /// If the internal is null. + /// This can occur if a derived type deletes it or does not create it. public bool Verify(byte[] input, byte[] signature, int length) { if (input == null) @@ -343,22 +370,31 @@ public override bool Verify(byte[] input, int inputOffset, int inputLength, byte } /// - /// This internal method is called from the AuthenticatedEncryptionProvider which passes in the algorithm that defines the size expected for the signature. + /// This internal method is called from the AuthenticatedEncryptionProvider which passes in the algorithm that defines + /// the size expected for the signature. /// The reason is the way the AuthenticationTag is validated. - /// For example when "A128CBC-HS256" is specified, SHA256 will used to create the HMAC and 32 bytes will be generated, but only the first 16 will be validated. + /// For example when "A128CBC-HS256" is specified, SHA256 will used to create the HMAC and 32 bytes will be generated, + /// but only the first 16 will be validated. /// /// The bytes to verify. - /// offset in to input bytes to caculate hash. + /// offset into the input bytes to calculate the hash. /// number of bytes of signature to use. /// signature to compare against. /// offset into signature array. - /// how many bytes to verfiy. + /// how many bytes to verify. /// algorithm passed by AuthenticatedEncryptionProvider. /// true if computed signature matches the signature parameter, false otherwise. #if NET6_0_OR_GREATER [SkipLocalsInit] #endif - internal bool Verify(byte[] input, int inputOffset, int inputLength, byte[] signature, int signatureOffset, int signatureLength, string algorithm) + internal bool Verify( + byte[] input, + int inputOffset, + int inputLength, + byte[] signature, + int signatureOffset, + int signatureLength, + string algorithm) { if (input == null || input.Length == 0) throw LogHelper.LogArgumentNullException(nameof(input)); @@ -448,7 +484,8 @@ internal bool Verify(byte[] input, int inputOffset, int inputLength, byte[] sign scoped Span hash; #if NET6_0_OR_GREATER - hash = stackalloc byte[keyedHashAlgorithm.HashSize / 8]; // only known algorithms are used, all of which have a small enough hash size to stackalloc + // only known algorithms are used, all of which have a small enough hash size to stackalloc + hash = stackalloc byte[keyedHashAlgorithm.HashSize / 8]; keyedHashAlgorithm.TryComputeHash(input.AsSpan(inputOffset, inputLength), hash, out int bytesWritten); Debug.Assert(bytesWritten == hash.Length); #else From e587fdc542fec65887e6979e1670d0034f92dd75 Mon Sep 17 00:00:00 2001 From: Westin Musser <127992899+westin-m@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:53:00 -0800 Subject: [PATCH 15/23] do not put the coverage report in a PR comment (#3048) --- .github/workflows/dotnetcore.yml | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index cfef45dc72..81e449eeea 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -50,21 +50,22 @@ jobs: shell: bash run: | cat CodeCoverage/SummaryGithub.md >> $GITHUB_STEP_SUMMARY - echo "COMMENT_CONTENT_ENV_VAR<> $GITHUB_ENV - echo $(cat CodeCoverage/SummaryGithub.md) >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV + # Temporarily disable commenting the coverage report + # echo "COMMENT_CONTENT_ENV_VAR<> $GITHUB_ENV + # echo $(cat CodeCoverage/SummaryGithub.md) >> $GITHUB_ENV + # echo "EOF" >> $GITHUB_ENV - - name: Comment coverage in PR - uses: actions/github-script@v7 - id: comment - with: - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: process.env.COMMENT_CONTENT_ENV_VAR - }) + # - name: Comment coverage in PR + # uses: actions/github-script@v7 + # id: comment + # with: + # script: | + # github.rest.issues.createComment({ + # issue_number: context.issue.number, + # owner: context.repo.owner, + # repo: context.repo.repo, + # body: process.env.COMMENT_CONTENT_ENV_VAR + # }) # Run baseline package validation - name: Pack From 4c5b41b975e35f60a75b306ec06315c6770716ec Mon Sep 17 00:00:00 2001 From: sruthikeerthi <73967733+sruke@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:55:02 -0800 Subject: [PATCH 16/23] Support long paths (#3049) Co-authored-by: Sruthi Keerthi Rangavajhula (from Dev Box) --- .github/workflows/dotnetcore.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 81e449eeea..16328cc5d0 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -24,8 +24,13 @@ jobs: build: runs-on: windows-latest continue-on-error: false + name: "Build and run unit tests" steps: + - name: Set git core.longpaths flag + run: | + git config --system core.longpaths true + - name: Checkout repository uses: actions/checkout@v4.1.1 @@ -36,7 +41,7 @@ jobs: - name: Strong name bypass run: | - regedit /s .\build\strongNameBypass.reg + regedit /s .\build\strongNameBypass.reg - name: Run the tests run: dotnet test Wilson.sln --collect:"XPlat Code Coverage" --settings:./build/CodeCoverage.runsettings From 1293a5dd16d23218c4d5c05099c142ba13b43fcd Mon Sep 17 00:00:00 2001 From: Westin Musser <127992899+westin-m@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:50:13 -0800 Subject: [PATCH 17/23] fix comment to match pre-condition (#3052) --- .../Configuration/ConfigurationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs index cd06188f0f..1f38022f33 100644 --- a/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs +++ b/src/Microsoft.IdentityModel.Protocols/Configuration/ConfigurationManager.cs @@ -235,7 +235,7 @@ public virtual async Task GetConfigurationAsync(CancellationToken cancel) /// /// This should be called when the configuration needs to be updated either from RequestRefresh or AutomaticRefresh /// The Caller should first check the state checking state using: - /// if (Interlocked.CompareExchange(ref _configurationRetrieverState, ConfigurationRetrieverIdle, ConfigurationRetrieverRunning) != ConfigurationRetrieverRunning). + /// if (Interlocked.CompareExchange(ref _configurationRetrieverState, ConfigurationRetrieverRunning, ConfigurationRetrieverIdle) == ConfigurationRetrieverIdle). /// private void UpdateCurrentConfiguration() { From 79460ca553ec87058017bc428852b138f786c2fb Mon Sep 17 00:00:00 2001 From: Jean-Marc Prieur Date: Wed, 4 Dec 2024 22:21:14 +0100 Subject: [PATCH 18/23] Update CHANGELOG.md (#3053) --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1c14e26d7..b994ce1945 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ See the [releases](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases) for details on bug fixes and added features. +8.3.0 +===== + +## New features + +### Work related to redesign of IdentityModel's token validation logic [#2711](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/2711) +* SAML and SAML2 new model validation: Token Replay. See [#2994](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/2994) +* Extensibility tests: Token Type - JWT ([#3030](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3030)), Issuer - SAML and SAML2 ([#3026](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3026)), Algorithm and Signature - JWT, SAML and SAML2 ([#3034](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3034)), Token Replay - JWT, SAML and SAML2 ([#3032](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3032)), Issuer signing key - JWT, SAML and SAML2 ([#3029](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/302)) +* Avoid code duplication in extensibility testing. See [#3041](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3041) +* Extensibility Testing: Refactor. See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3011 +* Remove duplicate code in extensibility tests. See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3044 + +## Bug fixes +* Fix bug with AadIssuerValidator. See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3042 +* Fixed SignedHttpRequest flaky test. See [#3037](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3037) + +## Fundamentals +* Install all .NET versions in pipeline to fix run tests task. See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3018 +* Changelog for 8.2.1. See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3009 +* Remove unnecessary AoT test project. See in https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3045 +* Fix powershell script for nuget update. See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3046 +* Update to next version. See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3010 +* Disable Coverage PR comments. See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3048 +* Updates GitHub Action to support long paths, See https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3049 +* Stack parameters to improve reading of code. by @brentschmaltz in https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3031 + +## New Contributors +* @ssmelov made their first contribution in https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/3042 + 8.2.1 ===== ### New features From bb6433b126502c813df2a605c78becbea33f1837 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Mon, 9 Dec 2024 19:20:31 +0000 Subject: [PATCH 19/23] Extensibility tests: Audience - JWT, SAML and SAML2 (#3027) * Handle potential exceptions thrown by the audience validation delegate on JWT, SAML, and SAML2 * Added cusotm audience validation delegates for testing (cherry picked from commit 2f336c2d8eb0ca6ac2d5966baf12c3c6d0d01a4d) * Added audience extensibility tests for JWT, SAML, and SAML2 (cherry picked from commit f5b4362f7241348c9c28e17eaae9c5c69e96b6e9) * Updated validation failure type position in tests * Handle success case as unexpected in audience extensibility tests * Removed duplicate test code adopting the refactored approach after merging dev --- ...nWebTokenHandler.ValidateToken.Internal.cs | 27 ++- ...rityTokenHandler.ValidateToken.Internal.cs | 36 ++- ...rityTokenHandler.ValidateToken.Internal.cs | 32 ++- .../InternalAPI.Unshipped.txt | 2 + .../LogMessages.cs | 1 + .../Validation/ValidationFailureType.cs | 5 + ...nWebTokenHandler.Extensibility.Audience.cs | 37 +++ .../CustomAudienceValidationDelegates.cs | 143 ++++++++++++ .../Tests/AudienceExtensibilityTestCases.cs | 217 ++++++++++++++++++ .../Tests/AudienceExtensibilityTheoryData.cs | 28 +++ ...rityTokenHandler.Extensibility.Audience.cs | 37 +++ ...rityTokenHandler.Extensibility.Audience.cs | 37 +++ 12 files changed, 579 insertions(+), 23 deletions(-) create mode 100644 test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Audience.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomAudienceValidationDelegates.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AudienceExtensibilityTestCases.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AudienceExtensibilityTheoryData.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Audience.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Audience.cs diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs index 2deb10bafd..cfa1e5c057 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.ValidateToken.Internal.cs @@ -267,13 +267,30 @@ private async ValueTask> ValidateJWSAsync( if (jsonWebToken.Audiences is not IList tokenAudiences) tokenAudiences = jsonWebToken.Audiences.ToList(); - ValidationResult audienceValidationResult = validationParameters.AudienceValidator( - tokenAudiences, jsonWebToken, validationParameters, callContext); + ValidationResult audienceValidationResult; + try + { + audienceValidationResult = validationParameters.AudienceValidator( + tokenAudiences, jsonWebToken, validationParameters, callContext); - if (!audienceValidationResult.IsValid) + if (!audienceValidationResult.IsValid) + { + StackFrame audienceValidationFailureStackFrame = StackFrames.AudienceValidationFailed ??= new StackFrame(true); + return audienceValidationResult.UnwrapError().AddStackFrame(audienceValidationFailureStackFrame); + } + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types { - StackFrame audienceValidationFailureStackFrame = StackFrames.AudienceValidationFailed ??= new StackFrame(true); - return audienceValidationResult.UnwrapError().AddStackFrame(audienceValidationFailureStackFrame); + return new AudienceValidationError( + new MessageDetail(TokenLogMessages.IDX10270), + ValidationFailureType.AudienceValidatorThrew, + typeof(SecurityTokenInvalidAudienceException), + ValidationError.GetCurrentStackFrame(), + tokenAudiences, + null, + ex); } ValidationResult issuerValidationResult; 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 23b4444503..ec438248be 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs @@ -211,18 +211,34 @@ internal virtual ValidationResult ValidateConditions( if (condition is SamlAudienceRestrictionCondition audienceRestriction) { - // AudienceRestriction.Audiences is an ICollection so we need make a conversion to List before calling our audience validator var audiencesAsList = audienceRestriction.Audiences.Select(static x => x.OriginalString).ToList(); - - var audienceValidationResult = validationParameters.AudienceValidator( - audiencesAsList, - samlToken, - validationParameters, - callContext); - - if (!audienceValidationResult.IsValid) - return audienceValidationResult.UnwrapError(); + ValidationResult audienceValidationResult; + + try + { + audienceValidationResult = validationParameters.AudienceValidator( + audiencesAsList, + samlToken, + validationParameters, + callContext); + + if (!audienceValidationResult.IsValid) + return audienceValidationResult.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 AudienceValidationError( + new MessageDetail(Tokens.LogMessages.IDX10270), + ValidationFailureType.AudienceValidatorThrew, + typeof(SecurityTokenInvalidAudienceException), + ValidationError.GetCurrentStackFrame(), + audiencesAsList, + validationParameters.ValidAudiences, + ex); + } validatedAudience = audienceValidationResult.UnwrapResult(); } 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 81ae5762f8..b2358e68ec 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs @@ -235,15 +235,31 @@ internal virtual ValidationResult ValidateConditions( if (audienceRestriction.Audiences is not List audiencesAsList) audiencesAsList = [.. audienceRestriction.Audiences]; - var audienceValidationResult = validationParameters.AudienceValidator( - audiencesAsList, - samlToken, - validationParameters, - callContext); - if (!audienceValidationResult.IsValid) + ValidationResult audienceValidationResult; + + try { - StackFrames.AudienceValidationFailed ??= new StackFrame(true); - return audienceValidationResult.UnwrapError().AddStackFrame(StackFrames.AudienceValidationFailed); + audienceValidationResult = validationParameters.AudienceValidator( + audiencesAsList, + samlToken, + validationParameters, + callContext); + + if (!audienceValidationResult.IsValid) + return audienceValidationResult.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 AudienceValidationError( + new MessageDetail(Tokens.LogMessages.IDX10270), + ValidationFailureType.AudienceValidatorThrew, + typeof(SecurityTokenInvalidAudienceException), + ValidationError.GetCurrentStackFrame(), + audiencesAsList, + validationParameters.ValidAudiences, + ex); } // Audience is valid, save it for later. diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt index c81cb4212e..cc4d32a13a 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.IDX10270 = "IDX10270: AudienceValidationDelegate 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 @@ -61,6 +62,7 @@ static Microsoft.IdentityModel.Tokens.TokenTypeValidationError.NullParameter(str 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.AlgorithmValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType +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.NoTokenAudiencesProvided -> Microsoft.IdentityModel.Tokens.ValidationFailureType diff --git a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs index 02062ddad4..d87ce1e01c 100644 --- a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs +++ b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs @@ -88,6 +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 IDX10270 = "IDX10270: AudienceValidationDelegate 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 f20011641c..5fc1fa5cc6 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs @@ -140,6 +140,11 @@ 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 audience validation delegate threw an exception. + /// + public static readonly ValidationFailureType AudienceValidatorThrew = new AudienceValidationFailure("AudienceValidatorThrew"); + /// /// 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.Audience.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Audience.cs new file mode 100644 index 0000000000..d0d7e02b77 --- /dev/null +++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Audience.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(GenerateAudienceExtensibilityTestCases), + parameters: ["JWT", 2], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_AudienceValidator_Extensibility( + AudienceExtensibilityTheoryData theoryData) + { + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_AudienceValidator_Extensibility)); + } + + public static TheoryData GenerateAudienceExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) + { + return ExtensibilityTesting.GenerateAudienceExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "JsonWebTokenHandler.ValidateToken.Internal.cs"); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomAudienceValidationDelegates.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomAudienceValidationDelegates.cs new file mode 100644 index 0000000000..8b5010a8be --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomAudienceValidationDelegates.cs @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using Microsoft.IdentityModel.Tokens; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils +{ + internal class CustomAudienceValidationDelegates + { + internal static ValidationResult CustomAudienceValidatorDelegate( + IList tokenAudiences, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + // Returns a CustomAudienceValidationError : AudienceValidationError + return new CustomAudienceValidationError( + new MessageDetail(nameof(CustomAudienceValidatorDelegate), null), + ValidationFailureType.AudienceValidationFailed, + typeof(SecurityTokenInvalidAudienceException), + ValidationError.GetCurrentStackFrame(), + tokenAudiences, + null); + } + + internal static ValidationResult CustomAudienceValidatorCustomExceptionDelegate( + IList tokenAudiences, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomAudienceValidationError( + new MessageDetail(nameof(CustomAudienceValidatorCustomExceptionDelegate), null), + ValidationFailureType.AudienceValidationFailed, + typeof(CustomSecurityTokenInvalidAudienceException), + ValidationError.GetCurrentStackFrame(), + tokenAudiences, + null); + } + + internal static ValidationResult CustomAudienceValidatorCustomExceptionCustomFailureTypeDelegate( + IList tokenAudiences, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomAudienceValidationError( + new MessageDetail(nameof(CustomAudienceValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomAudienceValidationError.CustomAudienceValidationFailureType, + typeof(CustomSecurityTokenInvalidAudienceException), + ValidationError.GetCurrentStackFrame(), + tokenAudiences, + null); + } + + internal static ValidationResult CustomAudienceValidatorUnknownExceptionDelegate( + IList tokenAudiences, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomAudienceValidationError( + new MessageDetail(nameof(CustomAudienceValidatorUnknownExceptionDelegate), null), + ValidationFailureType.AudienceValidationFailed, + typeof(NotSupportedException), + ValidationError.GetCurrentStackFrame(), + tokenAudiences, + null); + } + + internal static ValidationResult CustomAudienceValidatorWithoutGetExceptionOverrideDelegate( + IList tokenAudiences, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new CustomAudienceWithoutGetExceptionValidationOverrideError( + new MessageDetail(nameof(CustomAudienceValidatorWithoutGetExceptionOverrideDelegate), null), + typeof(CustomSecurityTokenInvalidAudienceException), + ValidationError.GetCurrentStackFrame(), + tokenAudiences, + null); + } + + internal static ValidationResult AudienceValidatorDelegate( + IList tokenAudiences, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new AudienceValidationError( + new MessageDetail(nameof(AudienceValidatorDelegate), null), + ValidationFailureType.AudienceValidationFailed, + typeof(SecurityTokenInvalidAudienceException), + ValidationError.GetCurrentStackFrame(), + tokenAudiences, + null); + } + + internal static ValidationResult AudienceValidatorThrows( + IList tokenAudiences, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + throw new CustomSecurityTokenInvalidAudienceException(nameof(AudienceValidatorThrows), null); + } + + internal static ValidationResult AudienceValidatorCustomAudienceExceptionTypeDelegate( + IList tokenAudiences, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new AudienceValidationError( + new MessageDetail(nameof(AudienceValidatorCustomAudienceExceptionTypeDelegate), null), + ValidationFailureType.AudienceValidationFailed, + typeof(CustomSecurityTokenInvalidAudienceException), + ValidationError.GetCurrentStackFrame(), + tokenAudiences, + null); + } + + internal static ValidationResult AudienceValidatorCustomExceptionTypeDelegate( + IList tokenAudiences, + SecurityToken? securityToken, + ValidationParameters validationParameters, + CallContext callContext) + { + return new AudienceValidationError( + new MessageDetail(nameof(AudienceValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.AudienceValidationFailed, + typeof(CustomSecurityTokenException), + ValidationError.GetCurrentStackFrame(), + tokenAudiences, + null); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AudienceExtensibilityTestCases.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AudienceExtensibilityTestCases.cs new file mode 100644 index 0000000000..d6d56aa50f --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AudienceExtensibilityTestCases.cs @@ -0,0 +1,217 @@ +// 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; +using System.Collections.Generic; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests +{ + public partial class ExtensibilityTesting + { + public static TheoryData GenerateAudienceExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames, + string stackFrameFileName) + { + var theoryData = new TheoryData(); + CallContext callContext = new CallContext(); + string issuerGuid = Guid.NewGuid().ToString(); + var audience = Default.Audience; + List tokenAudiences = [audience]; + + #region return CustomAudienceValidationError + // Test cases where delegate is overridden and return a CustomAudienceValidationError + // CustomAudienceValidationError : AudienceValidationError, ExceptionType: SecurityTokenInvalidAudienceException + theoryData.Add(new AudienceExtensibilityTheoryData( + "CustomAudienceValidatorDelegate", + tokenHandlerType, + audience, + CustomAudienceValidationDelegates.CustomAudienceValidatorDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidAudienceException), + nameof(CustomAudienceValidationDelegates.CustomAudienceValidatorDelegate)), + ValidationError = new CustomAudienceValidationError( + new MessageDetail( + nameof(CustomAudienceValidationDelegates.CustomAudienceValidatorDelegate), null), + ValidationFailureType.AudienceValidationFailed, + typeof(SecurityTokenInvalidAudienceException), + new StackFrame("CustomAudienceValidationDelegates.cs", 0), + tokenAudiences, + null) + }); + + // CustomAudienceValidationError : AudienceValidationError, ExceptionType: CustomSecurityTokenInvalidAudienceException : SecurityTokenInvalidAudienceException + theoryData.Add(new AudienceExtensibilityTheoryData( + "CustomAudienceValidatorCustomExceptionDelegate", + tokenHandlerType, + audience, + CustomAudienceValidationDelegates.CustomAudienceValidatorCustomExceptionDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAudienceException), + nameof(CustomAudienceValidationDelegates.CustomAudienceValidatorCustomExceptionDelegate)), + ValidationError = new CustomAudienceValidationError( + new MessageDetail( + nameof(CustomAudienceValidationDelegates.CustomAudienceValidatorCustomExceptionDelegate), null), + ValidationFailureType.AudienceValidationFailed, + typeof(CustomSecurityTokenInvalidAudienceException), + new StackFrame("CustomAudienceValidationDelegates.cs", 0), + tokenAudiences, + null), + }); + + // CustomAudienceValidationError : AudienceValidationError, ExceptionType: NotSupportedException : SystemException + theoryData.Add(new AudienceExtensibilityTheoryData( + "CustomAudienceValidatorUnknownExceptionDelegate", + tokenHandlerType, + audience, + CustomAudienceValidationDelegates.CustomAudienceValidatorUnknownExceptionDelegate, + extraStackFrames: extraStackFrames) + { + // CustomAudienceValidationError 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(CustomAudienceValidationDelegates.CustomAudienceValidatorUnknownExceptionDelegate))), + ValidationError = new CustomAudienceValidationError( + new MessageDetail( + nameof(CustomAudienceValidationDelegates.CustomAudienceValidatorUnknownExceptionDelegate), null), + ValidationFailureType.AudienceValidationFailed, + typeof(NotSupportedException), + new StackFrame("CustomAudienceValidationDelegates.cs", 0), + tokenAudiences, + null), + }); + + // CustomAudienceValidationError : AudienceValidationError, ExceptionType: NotSupportedException : SystemException, ValidationFailureType: CustomAudienceValidationFailureType + theoryData.Add(new AudienceExtensibilityTheoryData( + "CustomAudienceValidatorCustomExceptionCustomFailureTypeDelegate", + tokenHandlerType, + audience, + CustomAudienceValidationDelegates.CustomAudienceValidatorCustomExceptionCustomFailureTypeDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(CustomSecurityTokenInvalidAudienceException), + nameof(CustomAudienceValidationDelegates.CustomAudienceValidatorCustomExceptionCustomFailureTypeDelegate)), + ValidationError = new CustomAudienceValidationError( + new MessageDetail( + nameof(CustomAudienceValidationDelegates.CustomAudienceValidatorCustomExceptionCustomFailureTypeDelegate), null), + CustomAudienceValidationError.CustomAudienceValidationFailureType, + typeof(CustomSecurityTokenInvalidAudienceException), + new StackFrame("CustomAudienceValidationDelegates.cs", 0), + tokenAudiences, + null) // validAudiences + }); + #endregion + + #region return AudienceValidationError + // Test cases where delegate is overridden and return an AudienceValidationError + // AudienceValidationError : ValidationError, ExceptionType: SecurityTokenInvalidAudienceException + theoryData.Add(new AudienceExtensibilityTheoryData( + "AudienceValidatorDelegate", + tokenHandlerType, + audience, + CustomAudienceValidationDelegates.AudienceValidatorDelegate, + extraStackFrames: extraStackFrames) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidAudienceException), + nameof(CustomAudienceValidationDelegates.AudienceValidatorDelegate)), + ValidationError = new AudienceValidationError( + new MessageDetail( + nameof(CustomAudienceValidationDelegates.AudienceValidatorDelegate), null), + ValidationFailureType.AudienceValidationFailed, + typeof(SecurityTokenInvalidAudienceException), + new StackFrame("CustomAudienceValidationDelegates.cs", 0), + tokenAudiences, + null) + }); + + // AudienceValidationError : ValidationError, ExceptionType: CustomSecurityTokenInvalidAudienceException : SecurityTokenInvalidAudienceException + theoryData.Add(new AudienceExtensibilityTheoryData( + "AudienceValidatorCustomAudienceExceptionTypeDelegate", + tokenHandlerType, + audience, + CustomAudienceValidationDelegates.AudienceValidatorCustomAudienceExceptionTypeDelegate, + extraStackFrames: extraStackFrames) + { + // AudienceValidationError does not handle the exception type 'CustomSecurityTokenInvalidAudienceException' + ExpectedException = ExpectedException.SecurityTokenException( + LogHelper.FormatInvariant( + Tokens.LogMessages.IDX10002, // "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'."; + typeof(CustomSecurityTokenInvalidAudienceException), + nameof(CustomAudienceValidationDelegates.AudienceValidatorCustomAudienceExceptionTypeDelegate))), + ValidationError = new AudienceValidationError( + new MessageDetail( + nameof(CustomAudienceValidationDelegates.AudienceValidatorCustomAudienceExceptionTypeDelegate), null), + ValidationFailureType.AudienceValidationFailed, + typeof(CustomSecurityTokenInvalidAudienceException), + new StackFrame("CustomAudienceValidationDelegates.cs", 0), + tokenAudiences, + null) + }); + + // AudienceValidationError : ValidationError, ExceptionType: CustomSecurityTokenException : SystemException + theoryData.Add(new AudienceExtensibilityTheoryData( + "AudienceValidatorCustomExceptionTypeDelegate", + tokenHandlerType, + audience, + CustomAudienceValidationDelegates.AudienceValidatorCustomExceptionTypeDelegate, + extraStackFrames: extraStackFrames) + { + // AudienceValidationError 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(CustomAudienceValidationDelegates.AudienceValidatorCustomExceptionTypeDelegate))), + ValidationError = new AudienceValidationError( + new MessageDetail( + nameof(CustomAudienceValidationDelegates.AudienceValidatorCustomExceptionTypeDelegate), null), + ValidationFailureType.AudienceValidationFailed, + typeof(CustomSecurityTokenException), + new StackFrame("CustomAudienceValidationDelegates.cs", 0), + tokenAudiences, + null) + }); + + // AudienceValidationError : ValidationError, ExceptionType: SecurityTokenInvalidAudienceException, inner: CustomSecurityTokenInvalidAudienceException + theoryData.Add(new AudienceExtensibilityTheoryData( + "AudienceValidatorThrows", + tokenHandlerType, + audience, + CustomAudienceValidationDelegates.AudienceValidatorThrows, + extraStackFrames: extraStackFrames - 1) + { + ExpectedException = new ExpectedException( + typeof(SecurityTokenInvalidAudienceException), + string.Format(Tokens.LogMessages.IDX10270), + typeof(CustomSecurityTokenInvalidAudienceException)), + ValidationError = new AudienceValidationError( + new MessageDetail( + string.Format(Tokens.LogMessages.IDX10270), null), + ValidationFailureType.AudienceValidatorThrew, + typeof(SecurityTokenInvalidAudienceException), + new StackFrame(stackFrameFileName, 0), + tokenAudiences, + null, + new SecurityTokenInvalidAudienceException(nameof(CustomAudienceValidationDelegates.AudienceValidatorThrows)) + ) + }); + #endregion + + return theoryData; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AudienceExtensibilityTheoryData.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AudienceExtensibilityTheoryData.cs new file mode 100644 index 0000000000..7b1da2d0ad --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/AudienceExtensibilityTheoryData.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.IdentityModel.Tokens; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests +{ + public class AudienceExtensibilityTheoryData : ExtensibilityTheoryData + { + internal AudienceExtensibilityTheoryData( + string testId, + string tokenHandlerType, + string audience, + AudienceValidationDelegate audienceValidationDelegate, + int extraStackFrames) : base(testId, tokenHandlerType, extraStackFrames) + { + SecurityTokenDescriptor = new() + { + Issuer = Default.Issuer, + Audience = audience, + }; + + ValidationParameters.AudienceValidator = audienceValidationDelegate; + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Audience.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Audience.cs new file mode 100644 index 0000000000..254667ea18 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Audience.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(GenerateAudienceExtensibilityTestCases), + parameters: ["SAML2", 2], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_AudienceValidator_Extensibility( + AudienceExtensibilityTheoryData theoryData) + { + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_AudienceValidator_Extensibility)); + } + + public static TheoryData GenerateAudienceExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) + { + return ExtensibilityTesting.GenerateAudienceExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "Saml2SecurityTokenHandler.ValidateToken.Internal.cs"); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Audience.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Audience.cs new file mode 100644 index 0000000000..9e6285389f --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Audience.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(GenerateAudienceExtensibilityTestCases), + parameters: ["SAML", 2], + DisableDiscoveryEnumeration = true)] + public async Task ValidateTokenAsync_AudienceValidator_Extensibility( + AudienceExtensibilityTheoryData theoryData) + { + await ExtensibilityTesting.ValidateTokenAsync_Extensibility( + theoryData, + this, + nameof(ValidateTokenAsync_AudienceValidator_Extensibility)); + } + + public static TheoryData GenerateAudienceExtensibilityTestCases( + string tokenHandlerType, + int extraStackFrames) + { + return ExtensibilityTesting.GenerateAudienceExtensibilityTestCases( + tokenHandlerType, + extraStackFrames, + "SamlSecurityTokenHandler.ValidateToken.Internal.cs"); + } + } +} +#nullable restore From fe4abcf8a3ec28e86864380a3edecfe03f040a1a Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Mon, 9 Dec 2024 19:43:59 +0000 Subject: [PATCH 20/23] Extensibility tests: Lifetime - JWT, SAML and SAML2 (#3028) * Handle potential exception thrown by the lifetime validation delegate in JWT, SAML, and SAML2 * Added custom lifetime validation delegates for testing (cherry picked from commit 6f104dc645140c74e3a924d5cdb4fa94d225cfcd) * Added lifetime extensibility tests for JWT, SAML, and SAML2 (cherry picked from commit e6026384795b283941156d80ff4eb3e092ddcfe0) * Updated validation failure type position in tests * Added missing validation failure type to custom lifetime validation delegates * Handle success case as unexpected in lifetime extensibility tests * Removed duplicate test code adopting the refactored approach after merging dev --- ...nWebTokenHandler.ValidateToken.Internal.cs | 25 +- ...rityTokenHandler.ValidateToken.Internal.cs | 35 ++- ...rityTokenHandler.ValidateToken.Internal.cs | 33 ++- .../InternalAPI.Unshipped.txt | 2 + .../LogMessages.cs | 1 + .../Validation/ValidationFailureType.cs | 9 +- ...nWebTokenHandler.Extensibility.Lifetime.cs | 37 +++ .../CustomLifetimeValidationDelegates.cs | 159 +++++++++++++ .../Tests/LifetimeExtensibilityTestCases.cs | 215 ++++++++++++++++++ .../Tests/LifetimeExtensibilityTheoryData.cs | 31 +++ ...rityTokenHandler.Extensibility.Lifetime.cs | 37 +++ ...rityTokenHandler.Extensibility.Lifetime.cs | 37 +++ 12 files changed, 595 insertions(+), 26 deletions(-) create mode 100644 test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.Extensibility.Lifetime.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/CustomLifetimeValidationDelegates.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/LifetimeExtensibilityTestCases.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/LifetimeExtensibilityTheoryData.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Extensibility.Lifetime.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Extensibility.Lifetime.cs 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 From 039e8f8e032cdf0fe0cba8e205f4906019c876bd Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Mon, 9 Dec 2024 20:40:52 +0000 Subject: [PATCH 21/23] Implement lazy ClaimsIdentity creation from ValidatedToken on SAML and SAML2 on the new validation model (#3051) * Implemented the claimsidentity creation methods to allow ValidatedToken to lazily create the claims when accessed in SAML and SAML2 token handlers * Added tests, updated returned ValidatedToken to generate the right ClaimsIdentity * Addressed PR feedback --- .../InternalAPI.Unshipped.txt | 5 + ...SamlSecurityTokenHandler.ClaimsIdentity.cs | 85 +++++++++++++++++ ...rityTokenHandler.ValidateToken.Internal.cs | 22 +++-- ...aml2SecurityTokenHandler.ClaimsIdentity.cs | 44 +++++++++ ...rityTokenHandler.ValidateToken.Internal.cs | 22 +++-- .../SamlClaimsIdentityComparisonTestBase.cs | 91 +++++++++++++++++++ .../Tests/ExtensibilityTheoryData.cs | 4 +- ...okenHandler.cs => ITestingTokenHandler.cs} | 82 ++++++++++++++++- ...yTokenHandler.Comparison.ClaimsIdentity.cs | 23 +++++ ...yTokenHandler.Comparison.ClaimsIdentity.cs | 23 +++++ 10 files changed, 383 insertions(+), 18 deletions(-) create mode 100644 src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ClaimsIdentity.cs create mode 100644 src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ClaimsIdentity.cs create mode 100644 test/Microsoft.IdentityModel.TestUtils/SamlClaimsIdentityComparisonTestBase.cs rename test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/{IExtensibilityTestingTokenHandler.cs => ITestingTokenHandler.cs} (54%) create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Comparison.ClaimsIdentity.cs create mode 100644 test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Comparison.ClaimsIdentity.cs diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt index 6e9837ef35..55944f8e39 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens.Saml/InternalAPI.Unshipped.txt @@ -1,6 +1,7 @@ const Microsoft.IdentityModel.Tokens.Saml.LogMessages.IDX11402 = "IDX11402: Unable to read SamlSecurityToken. Exception thrown: '{0}'." -> string const Microsoft.IdentityModel.Tokens.Saml2.LogMessages.IDX13003 = "IDX13003: Unable to read Saml2SecurityToken. Exception thrown: '{0}'." -> string Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames +Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.CreateClaimsIdentity(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, string issuer) -> System.Security.Claims.ClaimsIdentity Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedAudience.get -> string Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidatedConditions.ValidatedAudience.set -> void @@ -12,12 +13,15 @@ Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateTokenAsync( Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> Microsoft.IdentityModel.Tokens.Saml.SamlValidationError Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.SamlValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException = null) -> void +Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.CreateClaimsIdentity(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, string issuer) -> System.Security.Claims.ClaimsIdentity Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateTokenAsync(Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateTokenAsync(string token, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task> Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.Saml2ValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException = null) -> void +override Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.CreateClaimsIdentityInternal(Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, string issuer) -> System.Security.Claims.ClaimsIdentity override Microsoft.IdentityModel.Tokens.Saml.SamlValidationError.GetException() -> System.Exception +override Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.CreateClaimsIdentityInternal(Microsoft.IdentityModel.Tokens.SecurityToken securityToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, string issuer) -> System.Security.Claims.ClaimsIdentity override Microsoft.IdentityModel.Tokens.Saml2.Saml2ValidationError.GetException() -> System.Exception static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.IssuerValidationFailed -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.StackFrames.SignatureValidationFailed -> System.Diagnostics.StackFrame @@ -44,6 +48,7 @@ static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrame static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.TokenNull -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.StackFrames.TokenValidationParametersNull -> System.Diagnostics.StackFrame static Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ValidateSignature(Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult +virtual Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ProcessStatements(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, string issuer, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters) -> System.Collections.Generic.IEnumerable virtual Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ReadSamlToken(string token, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult virtual Microsoft.IdentityModel.Tokens.Saml.SamlSecurityTokenHandler.ValidateConditions(Microsoft.IdentityModel.Tokens.Saml.SamlSecurityToken samlToken, Microsoft.IdentityModel.Tokens.ValidationParameters validationParameters, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult virtual Microsoft.IdentityModel.Tokens.Saml2.Saml2SecurityTokenHandler.ReadSaml2Token(string token, Microsoft.IdentityModel.Tokens.CallContext callContext) -> Microsoft.IdentityModel.Tokens.ValidationResult diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ClaimsIdentity.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ClaimsIdentity.cs new file mode 100644 index 0000000000..1e1cef4c4d --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ClaimsIdentity.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using Microsoft.IdentityModel.Logging; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml +{ + /// + /// A designed for creating and validating Saml Tokens. See: http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf + /// + public partial class SamlSecurityTokenHandler : SecurityTokenHandler + { + internal override ClaimsIdentity CreateClaimsIdentityInternal(SecurityToken securityToken, ValidationParameters validationParameters, string issuer) + { + return CreateClaimsIdentity((SamlSecurityToken)securityToken, validationParameters, issuer); + } + + internal ClaimsIdentity CreateClaimsIdentity(SamlSecurityToken samlToken, ValidationParameters validationParameters, string issuer) + { + if (samlToken == null) + throw LogHelper.LogArgumentNullException(nameof(samlToken)); + + if (samlToken.Assertion == null) + throw LogHelper.LogArgumentNullException(LogMessages.IDX11110); + + var actualIssuer = issuer; + if (string.IsNullOrWhiteSpace(issuer)) + actualIssuer = ClaimsIdentity.DefaultIssuer; + + IEnumerable identities = ProcessStatements( + samlToken, + actualIssuer, + validationParameters); + + return identities.First(); + } + + /// + /// Processes all statements to generate claims. + /// + /// A that will be used to create the claims. + /// The issuer. + /// The to be used for validating the token. + /// A containing the claims from the . + /// if the statement is not a . + internal virtual IEnumerable ProcessStatements(SamlSecurityToken samlToken, string issuer, ValidationParameters validationParameters) + { + if (samlToken == null) + throw LogHelper.LogArgumentNullException(nameof(samlToken)); + + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + var identityDict = new Dictionary(SamlSubjectEqualityComparer); + foreach (SamlStatement? item in samlToken.Assertion.Statements) + { + if (item is not SamlSubjectStatement statement) + throw LogHelper.LogExceptionMessage(new SamlSecurityTokenException(LogMessages.IDX11515)); + + if (!identityDict.TryGetValue(statement.Subject, out ClaimsIdentity? identity)) + { + identity = validationParameters.CreateClaimsIdentity(samlToken, issuer); + ProcessSubject(statement.Subject, identity, issuer); + identityDict.Add(statement.Subject, identity); + } + + if (statement is SamlAttributeStatement attrStatement) + ProcessAttributeStatement(attrStatement, identity, issuer); + else if (statement is SamlAuthenticationStatement authnStatement) + ProcessAuthenticationStatement(authnStatement, identity, issuer); + else if (statement is SamlAuthorizationDecisionStatement authzStatement) + ProcessAuthorizationDecisionStatement(authzStatement, identity, issuer); + else + ProcessCustomSubjectStatement(statement, identity, issuer); + } + + return identityDict.Values; + } + } +} +#nullable restore 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 d0572cd972..2e073084f8 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.ValidateToken.Internal.cs @@ -76,9 +76,11 @@ internal async Task> ValidateTokenAsync( if (!conditionsResult.IsValid) return conditionsResult.UnwrapError().AddCurrentStackFrame(); + ValidationResult issuerValidationResult; + try { - ValidationResult issuerValidationResult = await validationParameters.IssuerValidatorAsync( + issuerValidationResult = await validationParameters.IssuerValidatorAsync( samlToken.Issuer, samlToken, validationParameters, @@ -101,10 +103,10 @@ internal async Task> ValidateTokenAsync( ex); } + ValidationResult? tokenReplayValidationResult = null; + if (samlToken.Assertion.Conditions is not null) { - ValidationResult tokenReplayValidationResult; - try { tokenReplayValidationResult = validationParameters.TokenReplayValidator( @@ -113,8 +115,8 @@ internal async Task> ValidateTokenAsync( validationParameters, callContext); - if (!tokenReplayValidationResult.IsValid) - return tokenReplayValidationResult.UnwrapError().AddCurrentStackFrame(); + if (!tokenReplayValidationResult.Value.IsValid) + return tokenReplayValidationResult.Value.UnwrapError().AddCurrentStackFrame(); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) @@ -165,7 +167,15 @@ internal async Task> ValidateTokenAsync( ex); } - return new ValidatedToken(samlToken, this, validationParameters); + return new ValidatedToken(samlToken, this, validationParameters) + { + ValidatedAudience = conditionsResult.UnwrapResult().ValidatedAudience, + ValidatedLifetime = conditionsResult.UnwrapResult().ValidatedLifetime, + ValidatedIssuer = issuerValidationResult.UnwrapResult(), + ValidatedTokenReplayExpirationTime = tokenReplayValidationResult?.UnwrapResult(), + ValidatedSigningKey = signatureValidationResult.UnwrapResult(), + ValidatedSigningKeyLifetime = issuerSigningKeyValidationResult.UnwrapResult(), + }; } // ValidatedConditions is basically a named tuple but using a record struct better expresses the intent. diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ClaimsIdentity.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ClaimsIdentity.cs new file mode 100644 index 0000000000..f719f1ef17 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ClaimsIdentity.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Security.Claims; +using Microsoft.IdentityModel.Logging; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml2 +{ + /// + /// A designed for creating and validating Saml2 Tokens. See: http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf + /// + public partial class Saml2SecurityTokenHandler : SecurityTokenHandler + { + internal override ClaimsIdentity CreateClaimsIdentityInternal(SecurityToken securityToken, ValidationParameters validationParameters, string issuer) + { + return CreateClaimsIdentity((Saml2SecurityToken)securityToken, validationParameters, issuer); + } + + internal ClaimsIdentity CreateClaimsIdentity(Saml2SecurityToken samlToken, ValidationParameters validationParameters, string issuer) + { + if (samlToken == null) + throw LogHelper.LogArgumentNullException(nameof(samlToken)); + + if (samlToken.Assertion == null) + throw LogHelper.LogArgumentNullException(LogMessages.IDX13110); + + if (validationParameters == null) + throw LogHelper.LogArgumentNullException(nameof(validationParameters)); + + string actualIssuer = issuer; + if (string.IsNullOrWhiteSpace(issuer)) + actualIssuer = ClaimsIdentity.DefaultIssuer; + + ClaimsIdentity identity = validationParameters.CreateClaimsIdentity(samlToken, actualIssuer); + + ProcessSubject(samlToken.Assertion.Subject, identity, actualIssuer); + ProcessStatements(samlToken.Assertion.Statements, identity, actualIssuer); + + return identity; + } + } +} +#nullable restore 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 af3ee758ce..4b9f18d590 100644 --- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs +++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml2/Saml2SecurityTokenHandler.ValidateToken.Internal.cs @@ -80,9 +80,11 @@ internal async Task> ValidateTokenAsync( if (!conditionsResult.IsValid) return conditionsResult.UnwrapError().AddCurrentStackFrame(); + ValidationResult issuerValidationResult; + try { - ValidationResult issuerValidationResult = await validationParameters.IssuerValidatorAsync( + issuerValidationResult = await validationParameters.IssuerValidatorAsync( samlToken.Issuer, samlToken, validationParameters, @@ -105,10 +107,10 @@ internal async Task> ValidateTokenAsync( ex); } + ValidationResult? tokenReplayValidationResult = null; + if (samlToken.Assertion.Conditions is not null) { - ValidationResult tokenReplayValidationResult; - try { tokenReplayValidationResult = validationParameters.TokenReplayValidator( @@ -117,8 +119,8 @@ internal async Task> ValidateTokenAsync( validationParameters, callContext); - if (!tokenReplayValidationResult.IsValid) - return tokenReplayValidationResult.UnwrapError().AddCurrentStackFrame(); + if (!tokenReplayValidationResult.Value.IsValid) + return tokenReplayValidationResult.Value.UnwrapError().AddCurrentStackFrame(); } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) @@ -168,7 +170,15 @@ internal async Task> ValidateTokenAsync( ex); } - return new ValidatedToken(samlToken, this, validationParameters); + return new ValidatedToken(samlToken, this, validationParameters) + { + ValidatedAudience = conditionsResult.UnwrapResult().ValidatedAudience, + ValidatedLifetime = conditionsResult.UnwrapResult().ValidatedLifetime, + ValidatedIssuer = issuerValidationResult.UnwrapResult(), + ValidatedTokenReplayExpirationTime = tokenReplayValidationResult?.UnwrapResult(), + ValidatedSigningKey = signatureValidationResult.UnwrapResult(), + ValidatedSigningKeyLifetime = issuerSigningKeyValidationResult.UnwrapResult(), + }; } // ValidatedConditions is basically a named tuple but using a record struct better expresses the intent. diff --git a/test/Microsoft.IdentityModel.TestUtils/SamlClaimsIdentityComparisonTestBase.cs b/test/Microsoft.IdentityModel.TestUtils/SamlClaimsIdentityComparisonTestBase.cs new file mode 100644 index 0000000000..00f8656c52 --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/SamlClaimsIdentityComparisonTestBase.cs @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests; +using Microsoft.IdentityModel.Tokens; +using Microsoft.IdentityModel.Tokens.Saml; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +#nullable enable +namespace Microsoft.IdentityModel.TestUtils +{ + public partial class SamlClaimsIdentityComparisonTestBase + { + public static async Task ValidateTokenAsync_ClaimsIdentity_Comparison( + object testInstance, string methodName, string tokenHandlerType) + { + var context = new CompareContext($"{testInstance}.{methodName}"); + + List issuers = [Default.Issuer]; + List audiences = [Default.Audience]; + var signingKey = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2.Key; + + ValidationParameters validationParameters = CreateValidationParameters( + issuers, audiences, signingKey); + TokenValidationParameters tokenValidationParameters = CreateTokenValidationParameters( + issuers, audiences, signingKey); + + ITestingTokenHandler tokenHandler = ExtensibilityTheoryData + .CreateSecurityTokenHandlerForType(tokenHandlerType); + + DateTime utcNow = DateTime.UtcNow; + string token = tokenHandler.CreateStringToken(new() + { + Subject = Default.SamlClaimsIdentity, + Issuer = Default.Issuer, + Audience = Default.Audience, + SigningCredentials = KeyingMaterial.DefaultX509SigningCreds_2048_RsaSha2_Sha2, + IssuedAt = utcNow.AddHours(-1), + Expires = utcNow.AddHours(1), + NotBefore = utcNow.AddHours(-1), + }); + + ValidationResult validationResult = await tokenHandler.ValidateTokenAsync( + token, + validationParameters, + new CallContext(), + CancellationToken.None); + + TokenValidationResult tokenValidationResult = await tokenHandler.ValidateTokenAsync( + token, + tokenValidationParameters); + + IdentityComparer.AreBoolsEqual( + validationResult.IsValid, + tokenValidationResult.IsValid, context); + + IdentityComparer.AreClaimsIdentitiesEqual( + validationResult.UnwrapResult().ClaimsIdentity, + tokenValidationResult.ClaimsIdentity, context); + + TestUtilities.AssertFailIfErrors(context); + } + + static ValidationParameters CreateValidationParameters( + List issuers, + List audiences, + SecurityKey signingKey) + { + var validationParameters = new ValidationParameters(); + validationParameters.IssuerSigningKeys.Add(signingKey); + audiences.ForEach(validationParameters.ValidAudiences.Add); + issuers.ForEach(validationParameters.ValidIssuers.Add); + return validationParameters; + } + + static TokenValidationParameters CreateTokenValidationParameters( + List issuers, + List audiences, + SecurityKey signingKey) + { + var tokenValidationParameters = new TokenValidationParameters(); + tokenValidationParameters.ValidAudiences = audiences; + tokenValidationParameters.ValidIssuers = issuers; + tokenValidationParameters.IssuerSigningKey = signingKey; + return tokenValidationParameters; + } + } +} diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs index 6a2763c5c3..839e636701 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ExtensibilityTheoryData.cs @@ -25,7 +25,7 @@ internal ExtensibilityTheoryData( ValidationParameters = CreateValidationParametersSkippingValidations(); } - private IExtensibilityTestingTokenHandler CreateSecurityTokenHandlerForType(string tokenHandlerType) + internal static ITestingTokenHandler CreateSecurityTokenHandlerForType(string tokenHandlerType) { return tokenHandlerType switch { @@ -74,7 +74,7 @@ public SecurityTokenDescriptor SecurityTokenDescriptor set => _securityTokenDescriptor = PopulateSubjectForSecurityTokenDescriptor(value, _tokenHandlerType); } - internal IExtensibilityTestingTokenHandler TokenHandler { get; } + internal ITestingTokenHandler TokenHandler { get; } public bool IsValid { get; set; } diff --git a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IExtensibilityTestingTokenHandler.cs b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ITestingTokenHandler.cs similarity index 54% rename from test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IExtensibilityTestingTokenHandler.cs rename to test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ITestingTokenHandler.cs index 8d2bfd5a03..ebfb32d418 100644 --- a/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/IExtensibilityTestingTokenHandler.cs +++ b/test/Microsoft.IdentityModel.TestUtils/TokenValidationExtensibility/Tests/ITestingTokenHandler.cs @@ -14,8 +14,18 @@ namespace Microsoft.IdentityModel.TestUtils.TokenValidationExtensibility.Tests // This interface is used to test the extensibility of the ValidateTokenAsync method // in the JsonWebTokenHandler, SamlSecurityTokenHandler, and Saml2SecurityTokenHandler classes, // since the ValidateTokenAsync method with ValidationParameters is not part of any shared interface. - internal interface IExtensibilityTestingTokenHandler + internal interface ITestingTokenHandler { + Task> ValidateTokenAsync( + string token, + ValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken); + + Task ValidateTokenAsync( + string token, + TokenValidationParameters validationParameters); + Task> ValidateTokenAsync( SecurityToken token, ValidationParameters validationParameters, @@ -23,11 +33,12 @@ Task> ValidateTokenAsync( CancellationToken cancellationToken); SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor); + string CreateStringToken(SecurityTokenDescriptor tokenDescriptor); } // Because the ValidateTokenAsync method in the token handler subclasses is internal, we need // to create classes that implement the IValidateTokenAsyncResult interface to use in tests. - internal class JsonWebTokenHandlerWithResult : IExtensibilityTestingTokenHandler + internal class JsonWebTokenHandlerWithResult : ITestingTokenHandler { private readonly JsonWebTokenHandler _handler = new JsonWebTokenHandler(); @@ -44,13 +55,34 @@ public async Task> ValidateTokenAsync( return await _handler.ValidateTokenAsync(token, validationParameters, callContext, cancellationToken); } + public async Task> ValidateTokenAsync( + string token, + ValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken) + { + return await _handler.ValidateTokenAsync(token, validationParameters, callContext, cancellationToken); + } + + public async Task ValidateTokenAsync( + string token, + TokenValidationParameters validationParameters) + { + return await _handler.ValidateTokenAsync(token, validationParameters); + } + public SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) { return _handler.ReadToken(_handler.CreateToken(tokenDescriptor)); } + + public string CreateStringToken(SecurityTokenDescriptor tokenDescriptor) + { + return _handler.CreateToken(tokenDescriptor); + } } - internal class SamlSecurityTokenHandlerWithResult : IExtensibilityTestingTokenHandler + internal class SamlSecurityTokenHandlerWithResult : ITestingTokenHandler { private readonly SamlSecurityTokenHandler _handler = new SamlSecurityTokenHandler(); @@ -63,6 +95,22 @@ public async Task> ValidateTokenAsync( return await _handler.ValidateTokenAsync(token, validationParameters, callContext, cancellationToken); } + public async Task> ValidateTokenAsync( + string token, + ValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken) + { + return await _handler.ValidateTokenAsync(token, validationParameters, callContext, cancellationToken); + } + + public async Task ValidateTokenAsync( + string token, + TokenValidationParameters validationParameters) + { + return await _handler.ValidateTokenAsync(token, validationParameters); + } + public SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) { SamlSecurityToken token = (SamlSecurityToken)_handler.CreateToken(tokenDescriptor); @@ -70,9 +118,14 @@ public SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) // Reading the token from the CanonicalString will set the signature correctly. return _handler.ReadToken(token.Assertion.CanonicalString); } + + public string CreateStringToken(SecurityTokenDescriptor tokenDescriptor) + { + return ((SamlSecurityToken)_handler.CreateToken(tokenDescriptor)).Assertion.CanonicalString; + } } - internal class Saml2SecurityTokenHandlerWithResult : IExtensibilityTestingTokenHandler + internal class Saml2SecurityTokenHandlerWithResult : ITestingTokenHandler { private readonly Saml2SecurityTokenHandler _handler = new Saml2SecurityTokenHandler(); @@ -85,6 +138,22 @@ public async Task> ValidateTokenAsync( return await _handler.ValidateTokenAsync(token, validationParameters, callContext, cancellationToken); } + public async Task> ValidateTokenAsync( + string token, + ValidationParameters validationParameters, + CallContext callContext, + CancellationToken cancellationToken) + { + return await _handler.ValidateTokenAsync(token, validationParameters, callContext, cancellationToken); + } + + public async Task ValidateTokenAsync( + string token, + TokenValidationParameters validationParameters) + { + return await _handler.ValidateTokenAsync(token, validationParameters); + } + public SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) { Saml2SecurityToken token = (Saml2SecurityToken)_handler.CreateToken(tokenDescriptor); @@ -92,6 +161,11 @@ public SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor) // Reading the token from the CanonicalString will set the signature correctly. return _handler.ReadToken(token.Assertion.CanonicalString); } + + public string CreateStringToken(SecurityTokenDescriptor tokenDescriptor) + { + return ((Saml2SecurityToken)_handler.CreateToken(tokenDescriptor)).Assertion.CanonicalString; + } } } #nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Comparison.ClaimsIdentity.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Comparison.ClaimsIdentity.cs new file mode 100644 index 0000000000..29ae44e4d2 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/Saml2SecurityTokenHandler.Comparison.ClaimsIdentity.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.IdentityModel.TestUtils; +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml2.Tests +{ + public partial class Saml2SecurityTokenHandlerTests + { + [Fact] + public async Task ValidateTokenAsync_ClaimsIdentity_Comparison() + { + await SamlClaimsIdentityComparisonTestBase.ValidateTokenAsync_ClaimsIdentity_Comparison( + this, + nameof(ValidateTokenAsync_ClaimsIdentity_Comparison), + "SAML2"); + } + } +} +#nullable restore diff --git a/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Comparison.ClaimsIdentity.cs b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Comparison.ClaimsIdentity.cs new file mode 100644 index 0000000000..72cd31d127 --- /dev/null +++ b/test/Microsoft.IdentityModel.Tokens.Saml.Tests/SamlSecurityTokenHandler.Comparison.ClaimsIdentity.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Threading.Tasks; +using Microsoft.IdentityModel.TestUtils; +using Xunit; + +#nullable enable +namespace Microsoft.IdentityModel.Tokens.Saml.Tests +{ + public partial class SamlSecurityTokenHandlerTests + { + [Fact] + public async Task ValidateTokenAsync_ClaimsIdentity_Comparison() + { + await SamlClaimsIdentityComparisonTestBase.ValidateTokenAsync_ClaimsIdentity_Comparison( + this, + nameof(ValidateTokenAsync_ClaimsIdentity_Comparison), + "SAML"); + } + } +} +#nullable restore From 853b1fb1add24cd1dcd776b51259c080aefd6e88 Mon Sep 17 00:00:00 2001 From: Ignacio Inglese Date: Mon, 9 Dec 2024 21:18:23 +0000 Subject: [PATCH 22/23] Add logging to the new validation model (#3054) * Added Microsoft.Extensions.Logging.Abstractions to Microsoft.IdentityModel.Tokens to access ILogger * Added LoggingEventIds to store event ids used by the logging methods in M.IM.Tokens * Added logging to ValidationError and ValidatedToken to log the results obtained from ValidateTokenAsync after the fact. * Moved dependency version to dependencies.props * Update src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedToken.cs Co-authored-by: Westin Musser <127992899+westin-m@users.noreply.github.com> * Sorted dependency versions based on PR feedback --------- Co-authored-by: Westin Musser <127992899+westin-m@users.noreply.github.com> --- build/dependencies.props | 1 + .../InternalAPI.Unshipped.txt | 6 ++ .../LoggingEventId.cs | 14 ++++ .../Microsoft.IdentityModel.Tokens.csproj | 4 + .../Results/Details/ValidationError.cs | 27 +++++++ .../Validation/Results/ValidatedToken.cs | 77 ++++++++++++++++++- 6 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 src/Microsoft.IdentityModel.Tokens/LoggingEventId.cs diff --git a/build/dependencies.props b/build/dependencies.props index 3acadea8e2..e7f8afdb8c 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,6 +5,7 @@ 3.3.4 8.0.1 4.5.0 + 8.0.2 1.0.0 2.0.3 13.0.3 diff --git a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt index 5a8febaf08..c05846e38a 100644 --- a/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt +++ b/src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt @@ -27,6 +27,7 @@ Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedValidationPar Microsoft.IdentityModel.Tokens.LifetimeValidationError.Expires.get -> System.DateTime? Microsoft.IdentityModel.Tokens.LifetimeValidationError.LifetimeValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.DateTime? notBefore, System.DateTime? expires, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.LifetimeValidationError.NotBefore.get -> System.DateTime? +Microsoft.IdentityModel.Tokens.LoggingEventId Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTokenInvalidOperationException() -> void Microsoft.IdentityModel.Tokens.SecurityTokenInvalidOperationException.SecurityTokenInvalidOperationException(string message) -> void @@ -43,13 +44,16 @@ Microsoft.IdentityModel.Tokens.TokenTypeValidationError.InvalidTokenType.get -> 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.TokenValidationParameters.TimeProvider.get -> System.TimeProvider Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.set -> void +Microsoft.IdentityModel.Tokens.ValidatedToken.Log(Microsoft.Extensions.Logging.ILogger logger) -> void Microsoft.IdentityModel.Tokens.ValidationError.AddCurrentStackFrame(string filePath = "", int lineNumber = 0, int skipFrames = 1) -> Microsoft.IdentityModel.Tokens.ValidationError Microsoft.IdentityModel.Tokens.ValidationError.GetException(System.Type exceptionType, System.Exception innerException) -> System.Exception +Microsoft.IdentityModel.Tokens.ValidationError.Log(Microsoft.Extensions.Logging.ILogger logger) -> void Microsoft.IdentityModel.Tokens.ValidationError.ValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, Microsoft.IdentityModel.Tokens.ValidationFailureType validationFailureType, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, System.Exception innerException = null) -> void Microsoft.IdentityModel.Tokens.ValidationParameters.TokenTypeValidator.get -> Microsoft.IdentityModel.Tokens.TokenTypeValidationDelegate Microsoft.IdentityModel.Tokens.ValidationParameters.TokenTypeValidator.set -> void Microsoft.IdentityModel.Tokens.ValidationResult.Error.get -> Microsoft.IdentityModel.Tokens.ValidationError Microsoft.IdentityModel.Tokens.ValidationResult.IsValid.get -> bool +Microsoft.IdentityModel.Tokens.ValidationResult.Log(Microsoft.Extensions.Logging.ILogger logger) -> void Microsoft.IdentityModel.Tokens.ValidationResult.Result.get -> TResult override Microsoft.IdentityModel.Tokens.AlgorithmValidationError.GetException() -> System.Exception override Microsoft.IdentityModel.Tokens.IssuerSigningKeyValidationError.GetException() -> System.Exception @@ -62,6 +66,8 @@ static Microsoft.IdentityModel.Tokens.TokenReplayValidationError.NullParameter(s 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.LoggingEventId.TokenValidationFailed -> Microsoft.Extensions.Logging.EventId +static readonly Microsoft.IdentityModel.Tokens.LoggingEventId.TokenValidationSucceeded -> Microsoft.Extensions.Logging.EventId static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.AlgorithmValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.AudienceValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.IssuerSigningKeyValidatorThrew -> Microsoft.IdentityModel.Tokens.ValidationFailureType diff --git a/src/Microsoft.IdentityModel.Tokens/LoggingEventId.cs b/src/Microsoft.IdentityModel.Tokens/LoggingEventId.cs new file mode 100644 index 0000000000..aa682a3dd5 --- /dev/null +++ b/src/Microsoft.IdentityModel.Tokens/LoggingEventId.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Extensions.Logging; + +namespace Microsoft.IdentityModel.Tokens +{ + internal static class LoggingEventId + { + // TokenValidation EventIds 100+ + internal static readonly EventId TokenValidationFailed = new(100, "TokenValidationFailed"); + internal static readonly EventId TokenValidationSucceeded = new(101, "TokenValidationSucceeded"); + } +} diff --git a/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj b/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj index 459a772334..2e3b454bbf 100644 --- a/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj +++ b/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj @@ -72,5 +72,9 @@ + + + + diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs index fb8481d1ac..b4e0a6714f 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/Details/ValidationError.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; +using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Logging; #nullable enable @@ -183,6 +184,11 @@ internal Exception GetException(Type exceptionType, Exception? innerException) return exception; } + internal void Log(ILogger logger) + { + Logger.TokenValidationFailed(logger, FailureType.Name, MessageDetail.Message); + } + internal static ValidationError NullParameter(string parameterName, StackFrame stackFrame) => new( MessageDetail.NullParameter(parameterName), ValidationFailureType.NullArgument, @@ -261,6 +267,27 @@ internal static StackFrame GetCurrentStackFrame( // ConcurrentDictionary is thread-safe and only locks when adding a new item. private static ConcurrentDictionary CachedStackFrames { get; } = new(); + + private static class Logger + { + private static readonly Action s_tokenValidationFailed = + LoggerMessage.Define( + LogLevel.Information, + LoggingEventId.TokenValidationFailed, + "[MsIdentityModel] The token validation was unsuccessful due to: {ValidationFailureType} " + + "Error message provided: {ValidationErrorMessage}"); + + /// + /// Logger for handling failures in token validation. + /// + /// ILogger. + /// The cause of the failure. + /// The message provided as part of the failure. + public static void TokenValidationFailed( + ILogger logger, + string validationFailureType, + string messageDetail) => s_tokenValidationFailed(logger, validationFailureType, messageDetail, null); + } } } #nullable restore diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedToken.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedToken.cs index 3722fc4e8e..7eccd96c75 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedToken.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Results/ValidatedToken.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Security.Claims; using System.Threading; +using Microsoft.Extensions.Logging; #nullable enable namespace Microsoft.IdentityModel.Tokens @@ -34,11 +35,17 @@ internal ValidatedToken( /// /// Logs the validation result. /// -#pragma warning disable CA1822 // Mark members as static - public void Log() -#pragma warning restore CA1822 // Mark members as static + public void Log(ILogger logger) { - // TODO - Do we need this, how will it work? + Logger.TokenValidationSucceeded( + logger, + ValidatedAudience ?? "none", + ValidatedLifetime, + ValidatedIssuer, + ValidatedTokenType, + ValidatedSigningKey?.KeyId ?? "none", + ActorValidationResult is not null + ); } public SecurityToken SecurityToken { get; private set; } @@ -168,6 +175,68 @@ private object ClaimsIdentitySyncObj } } #endregion + + #region Logging + private static class Logger + { + private static readonly Action s_tokenValidationFailed = + LoggerMessage.Define( + LogLevel.Information, + LoggingEventId.TokenValidationFailed, + "[MsIdentityModel] The token validation was unsuccessful due to: {ValidationFailureType} " + + "Error message provided: {ValidationErrorMessage}"); + + /// + /// Logger for handling failures in token validation. + /// + /// ILogger. + /// The cause of the failure. + /// The message provided as part of the failure. + public static void TokenValidationFailed( + ILogger logger, + ValidationFailureType validationFailureType, + MessageDetail messageDetail) => s_tokenValidationFailed(logger, validationFailureType.Name, messageDetail.Message, null); + + private static readonly Action s_tokenValidationSucceeded = + LoggerMessage.Define( + LogLevel.Debug, + LoggingEventId.TokenValidationSucceeded, + "[MsIdentityModel] The token validation was successful. " + + "Validated audience: {ValidatedAudience} " + + "Validated lifetime: {ValidatedLifetime} " + + "Validated issuer: {ValidatedIssuer} " + + "Validated token type: {ValidatedTokenType} " + + "Validated signing key id: {ValidatedSigningKeyId} " + + "Actor was validated: {ActorWasValidated}"); + + /// + /// Logger for handling successful token validation. + /// + /// The instance to be used to log. + /// The audience that was validated. + /// The lifetime that was validated. + /// The issuer that was validated. + /// The token type that was validated. + /// The signing key id that was validated. + /// Whether the actor was validated. + public static void TokenValidationSucceeded( + ILogger logger, + string validatedAudience, + ValidatedLifetime? validatedLifetime, + ValidatedIssuer? validatedIssuer, + ValidatedTokenType? validatedTokenType, + string validatedSigningKeyId, + bool actorWasValidated) => s_tokenValidationSucceeded( + logger, + validatedAudience, + validatedLifetime, + validatedIssuer, + validatedTokenType, + validatedSigningKeyId, + actorWasValidated, + null); + } + #endregion } } #nullable disable From cba76668615ae15f9fec8dc265eef1bcf6d8ca29 Mon Sep 17 00:00:00 2001 From: BrentSchmaltz Date: Tue, 10 Dec 2024 11:13:08 -0800 Subject: [PATCH 23/23] update version (#3057) Co-authored-by: id4s --- build/version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/version.props b/build/version.props index 078047415a..7af60d8267 100644 --- a/build/version.props +++ b/build/version.props @@ -2,7 +2,7 @@ - 8.2.2 + 8.3.1 preview-$([System.DateTime]::Now.AddYears(-2019).Year)$([System.DateTime]::Now.ToString("MMddHHmmss"))