Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10002 = "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'." -> string
Microsoft.IdentityModel.Tokens.AudienceValidationError.InvalidAudiences.get -> System.Collections.Generic.IList<string>
2 changes: 2 additions & 0 deletions src/Microsoft.IdentityModel.Tokens/LogMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ internal static class LogMessages
// general
public const string IDX10000 = "IDX10000: The parameter '{0}' cannot be a 'null' or an empty object. ";
public const string IDX10001 = "IDX10001: Invalid argument '{0}'. Argument must be of type '{1}'.";
// TODO: Confirm the wording of this message. Possibly create a wiki page to refer users to for more information.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let make sure we have a tracking GitHub issue, otherwise we might forget. agree on an aka.ms link

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public const string IDX10002 = "IDX10002: Unknown exception type returned. Type: '{0}'. Message: '{1}'.";

// properties, configuration
public const string IDX10101 = "IDX10101: MaximumTokenSizeInBytes must be greater than zero. value: '{0}'";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,19 @@ public AudienceValidationError(
_invalidAudiences = invalidAudiences;
}

internal override void AddAdditionalInformation(ISecurityTokenException exception)
{
if (exception is SecurityTokenInvalidAudienceException invalidAudienceException)
invalidAudienceException.InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(_invalidAudiences);
}

/// <summary>
/// Creates an instance of an <see cref="Exception"/> using <see cref="ValidationError"/>
/// </summary>
/// <returns>An instance of an Exception.</returns>
public override Exception GetException()
internal override Exception GetException()
{
return new SecurityTokenInvalidAudienceException(MessageDetail.Message) { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(_invalidAudiences) };
if (ExceptionType == typeof(SecurityTokenInvalidAudienceException))
return new SecurityTokenInvalidAudienceException(MessageDetail.Message) { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(_invalidAudiences) };

return base.GetException();
}

internal IList<string>? InvalidAudiences => _invalidAudiences;
}
}
#nullable restore
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ internal IssuerValidationError(
_invalidIssuer = invalidIssuer;
}

public override Exception GetException()
internal override Exception GetException()
{
if (ExceptionType == typeof(SecurityTokenInvalidIssuerException))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public LifetimeValidationError(
/// Creates an instance of an <see cref="Exception"/> using <see cref="ValidationError"/>
/// </summary>
/// <returns>An instance of an Exception.</returns>
public override Exception GetException()
internal override Exception GetException()
{
if (ExceptionType == typeof(SecurityTokenNoExpirationException))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.IdentityModel.Logging;

namespace Microsoft.IdentityModel.Tokens
{
Expand Down Expand Up @@ -81,7 +82,7 @@ internal ValidationError(
/// Creates an instance of an <see cref="Exception"/> using <see cref="ValidationError"/>
/// </summary>
/// <returns>An instance of an Exception.</returns>
public virtual Exception GetException()
internal virtual Exception GetException()
{
Exception exception = GetException(ExceptionType, InnerException);

Expand Down Expand Up @@ -144,6 +145,12 @@ private Exception GetException(Type exceptionType, Exception innerException)
exception = new SecurityTokenException(MessageDetail.Message);
else if (exceptionType == typeof(SecurityTokenKeyWrapException))
exception = new SecurityTokenKeyWrapException(MessageDetail.Message);
else
{
// Exception type is unknown
var message = LogHelper.FormatInvariant(LogMessages.IDX10002, exceptionType, MessageDetail.Message);
exception = new SecurityTokenException(message);
}
}
else
{
Expand Down Expand Up @@ -193,6 +200,12 @@ private Exception GetException(Type exceptionType, Exception innerException)
exception = new SecurityTokenException(MessageDetail.Message, actualException);
else if (exceptionType == typeof(SecurityTokenKeyWrapException))
exception = new SecurityTokenKeyWrapException(MessageDetail.Message, actualException);
else
{
// Exception type is unknown
var message = LogHelper.FormatInvariant(LogMessages.IDX10002, exceptionType, MessageDetail.Message);
exception = new SecurityTokenException(message, actualException);
}
}

return exception;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.TestUtils;
using Microsoft.IdentityModel.Tokens;
using Xunit;

namespace Microsoft.IdentityModel.JsonWebTokens.Tests
{
public partial class JsonWebTokenHandlerValidateTokenAsyncTests
{
[Theory, MemberData(nameof(ValidateTokenAsync_Audience_ExtensibilityTestCases), DisableDiscoveryEnumeration = true)]
public async Task ValidateTokenAsync_Audience_Extensibility(ValidateTokenAsyncAudienceExtensibilityTheoryData theoryData)
{
var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_Audience_Extensibility)}", theoryData);

string jwtString = CreateTokenWithAudience(theoryData.Audience);
var handler = new JsonWebTokenHandler();

ValidationResult<ValidatedToken> validationResult;

if (theoryData.ThrownException is null)
{
validationResult = await handler.ValidateTokenAsync(
jwtString, theoryData.ValidationParameters!, theoryData.CallContext, CancellationToken.None);
}
else
{
// The exception is thrown by the delegate, so we catch it here.
// Outside of testing, this could be a catch block in the calling code.
var exception = await Assert.ThrowsAsync<CustomInvalidAudienceException>(async () =>
{
validationResult = await handler.ValidateTokenAsync(
jwtString, theoryData.ValidationParameters!, theoryData.CallContext, CancellationToken.None);
});

theoryData.ThrownException.ProcessException(exception, context);
return;
}

if (validationResult.IsSuccess != theoryData.ExpectedIsValid)
context.AddDiff($"validationResult.IsSuccess != theoryData.ExpectedIsValid");

if (validationResult.IsSuccess)
{
theoryData.ExpectedException.ProcessNoException(context);

IdentityComparer.AreStringsEqual(validationResult.UnwrapResult().ValidatedAudience, theoryData.Audience, context);
}
else
{
theoryData.ExpectedException.ProcessException(validationResult.UnwrapError().GetException(), context);

if (validationResult.UnwrapError().GetException() is SecurityTokenInvalidAudienceException audienceException)
{
if (theoryData.ExpectedInvalidAudience is not null)
IdentityComparer.AreStringsEqual(audienceException.InvalidAudience, theoryData.ExpectedInvalidAudience, context);
}

TestUtilities.AssertFailIfErrors(context);
}
}

public static TheoryData<ValidateTokenAsyncAudienceExtensibilityTheoryData> ValidateTokenAsync_Audience_ExtensibilityTestCases
{
get
{
return new TheoryData<ValidateTokenAsyncAudienceExtensibilityTheoryData>
{
new ValidateTokenAsyncAudienceExtensibilityTheoryData("DefaultDelegate_Valid_AudiencesMatch")
{
Audience = Default.Audience,
ValidationParameters = CreateValidationParameters([Default.Audience], audienceValidationDelegate: null),
},
new ValidateTokenAsyncAudienceExtensibilityTheoryData("DefaultDelegate_Invalid_AudiencesDontMatch")
{
ValidationParameters = CreateValidationParameters([Default.Audience], audienceValidationDelegate: null),
Audience = "CustomAudience",
ExpectedIsValid = false,
ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10215:"),
},
new ValidateTokenAsyncAudienceExtensibilityTheoryData("CustomDelegate_Valid_DelegateReturnsAudience")
{
Audience = Default.Audience,
ValidationParameters = CreateValidationParameters([Default.Audience], audienceValidationDelegate: delegate
(IList<string> audiences,
SecurityToken? securityToken,
ValidationParameters validationParameters,
CallContext callContext)
{
return "CustomAudience";
}),
},
new ValidateTokenAsyncAudienceExtensibilityTheoryData(
"CustomDelegate_Invalid_DelegateReturnsValidationErrorWithDefaultExceptionType")
{
Audience = Default.Audience,
ValidationParameters = CreateValidationParameters([Default.Audience], audienceValidationDelegate: delegate
(IList<string> audiences,
SecurityToken? securityToken,
ValidationParameters validationParameters,
CallContext callContext)
{
return new AudienceValidationError(
new MessageDetail("Custom message from the delegate."),
typeof(SecurityTokenInvalidAudienceException),
new StackFrame(true),
[Default.Audience]);
}),
ExpectedIsValid = false,
ExpectedException = new ExpectedException(typeof(SecurityTokenInvalidAudienceException), "Custom message from the delegate."),
ExpectedInvalidAudience = Default.Audience,
},
new ValidateTokenAsyncAudienceExtensibilityTheoryData(
"CustomDelegate_Invalid_DelegateReturnsValidationErrorWithCustomExceptionType_NoCustomValidationError")
{
Audience = Default.Audience,
ValidationParameters = CreateValidationParameters([Default.Audience], audienceValidationDelegate: delegate
(IList<string> audiences,
SecurityToken? securityToken,
ValidationParameters validationParameters,
CallContext callContext)
{
return new AudienceValidationError(
new MessageDetail("Custom message from the delegate."),
typeof(CustomInvalidAudienceException),
new StackFrame(true),
[Default.Audience]);
}),
ExpectedIsValid = false,
// The delegate returns a custom exception but does not implement a custom ValidationError.
ExpectedException = ExpectedException.SecurityTokenException("IDX10002:"),
ExpectedInvalidAudience = Default.Audience,
},
new ValidateTokenAsyncAudienceExtensibilityTheoryData(
"CustomDelegate_Invalid_DelegateReturnsValidationErrorWithCustomExceptionType_CustomValidationErrorUsed")
{
Audience = Default.Audience,
ValidationParameters = CreateValidationParameters([Default.Audience], audienceValidationDelegate: delegate
(IList<string> audiences,
SecurityToken? securityToken,
ValidationParameters validationParameters,
CallContext callContext)
{
return new CustomAudienceValidationError(
new MessageDetail("Custom message from the delegate."),
typeof(CustomInvalidAudienceException),
new StackFrame(true),
[Default.Audience]);
}),
ExpectedIsValid = false,
// The delegate uses a custom validation error that implements GetException to return the custom exception.
ExpectedException = new ExpectedException(typeof(CustomInvalidAudienceException), "Custom message from the delegate."),
ExpectedInvalidAudience = Default.Audience,
},
new ValidateTokenAsyncAudienceExtensibilityTheoryData("CustomDelegate_Invalid_DelegateThrows")
{
Audience = Default.Audience,
ValidationParameters = CreateValidationParameters([Default.Audience], audienceValidationDelegate: delegate
(IList<string> audiences,
SecurityToken? securityToken,
ValidationParameters validationParameters,
CallContext callContext)
{
throw new CustomInvalidAudienceException("Custom exception from the delegate.");
}),
ExpectedIsValid = false,
ThrownException = new ExpectedException(typeof(CustomInvalidAudienceException), "Custom exception from the delegate."),
},
};

static ValidationParameters CreateValidationParameters(
List<string>? audiences,
AudienceValidationDelegate? audienceValidationDelegate)
{
ValidationParameters validationParameters = new ValidationParameters();
audiences?.ForEach(audience => validationParameters.ValidAudiences.Add(audience));

if (audienceValidationDelegate is not null)
validationParameters.AudienceValidator = audienceValidationDelegate;

// Skip all validations except audience
validationParameters.AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation;
validationParameters.IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation;
validationParameters.IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation;
validationParameters.LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation;
validationParameters.SignatureValidator = SkipValidationDelegates.SkipSignatureValidation;
validationParameters.TypeValidator = SkipValidationDelegates.SkipTokenTypeValidation;

return validationParameters;
}
}
}

public class ValidateTokenAsyncAudienceExtensibilityTheoryData : ValidateTokenAsyncBaseTheoryData
{
public ValidateTokenAsyncAudienceExtensibilityTheoryData(string testId) : base(testId) { }

public string? Audience { get; internal set; } = Default.Audience;

public string? ExpectedInvalidAudience { get; internal set; } = null;

internal AudienceValidationDelegate? AudienceValidationDelegate { get; set; }

public ExpectedException? ThrownException { get; internal set; } = null;
}

private class CustomInvalidAudienceException : SecurityTokenInvalidAudienceException
{
public CustomInvalidAudienceException(string message)
: base(message)
{
}
}

private class CustomAudienceValidationError : AudienceValidationError
{
public CustomAudienceValidationError(MessageDetail messageDetail,
Type exceptionType,
StackFrame stackFrame,
IList<string>? invalidAudiences) : base(messageDetail, exceptionType, stackFrame, invalidAudiences)
{
}

internal override Exception GetException()
{
if (ExceptionType == typeof(CustomInvalidAudienceException))
return new CustomInvalidAudienceException(MessageDetail.Message) { InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(InvalidAudiences) };

return base.GetException();
}
}
}
}
#nullable restore
Loading