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