diff --git a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.cs b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.cs
index 9b70430151..a75c33da3a 100644
--- a/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.cs
+++ b/src/Microsoft.IdentityModel.Tokens.Saml/Saml/SamlSecurityTokenHandler.cs
@@ -880,7 +880,7 @@ protected virtual void SetDelegateFromAttribute(SamlAttribute attribute, ClaimsI
/// .
/// The being validated.
/// required for validation.
- /// see for additional details.
+ /// see for additional details.
protected virtual void ValidateAudience(IEnumerable audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
Validators.ValidateAudience(audiences, securityToken, validationParameters);
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/AudienceValidationResult.cs b/src/Microsoft.IdentityModel.Tokens/Validation/AudienceValidationResult.cs
new file mode 100644
index 0000000000..024cca68ba
--- /dev/null
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/AudienceValidationResult.cs
@@ -0,0 +1,67 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+
+namespace Microsoft.IdentityModel.Tokens
+{
+ ///
+ /// Contains the result of validating the audiences from a .
+ /// The contains a collection of for each step in the token validation.
+ ///
+ internal class AudienceValidationResult : ValidationResult
+ {
+ private Exception _exception;
+
+ ///
+ /// Creates an instance of .
+ ///
+ /// is the audience that was validated successfully.
+ public AudienceValidationResult(string audience) : base(ValidationFailureType.ValidationSucceeded)
+ {
+ IsValid = true;
+ Audience = audience;
+ }
+
+ ///
+ /// Creates an instance of
+ ///
+ /// is the audience that was intended to be validated.
+ /// is the that occurred during validation.
+ /// is the that occurred during validation.
+ public AudienceValidationResult(string audience, ValidationFailureType validationFailure, ExceptionDetail exceptionDetail)
+ : base(validationFailure, exceptionDetail)
+ {
+ IsValid = false;
+ Audience = audience;
+ }
+
+ ///
+ /// Gets the that occurred during validation.
+ ///
+ public override Exception Exception
+ {
+ get
+ {
+ if (_exception != null || ExceptionDetail == null)
+ return _exception;
+
+ HasValidOrExceptionWasRead = true;
+ _exception = ExceptionDetail.GetException();
+ if (_exception is SecurityTokenInvalidAudienceException securityTokenInvalidAudienceException)
+ {
+ securityTokenInvalidAudienceException.InvalidAudience = Audience;
+ securityTokenInvalidAudienceException.ExceptionDetail = ExceptionDetail;
+ securityTokenInvalidAudienceException.Source = "Microsoft.IdentityModel.Tokens";
+ }
+
+ return _exception;
+ }
+ }
+
+ ///
+ /// Gets the audience that was validated or intended to be validated.
+ ///
+ public string Audience { get; }
+ }
+}
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs
index 3d872bb63a..4c561994fb 100644
--- a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationFailureType.cs
@@ -33,6 +33,12 @@ private class NullArgumentFailure : ValidationFailureType { internal NullArgumen
public static readonly ValidationFailureType IssuerValidationFailed = new IssuerValidationFailure("IssuerValidationFailed");
private class IssuerValidationFailure : ValidationFailureType { internal IssuerValidationFailure(string name) : base(name) { } }
+ ///
+ /// Defines a type that represents that audience validation failed.
+ ///
+ public static readonly ValidationFailureType AudienceValidationFailed = new AudienceValidationFailure("AudienceValidationFailed");
+ private class AudienceValidationFailure : ValidationFailureType { internal AudienceValidationFailure(string name) : base(name) { } }
+
///
/// Defines a type that represents that no evaluation has taken place.
///
diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs
index 9c41082c53..e56c166e70 100644
--- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs
+++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs
@@ -3,12 +3,29 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using Microsoft.IdentityModel.Abstractions;
using Microsoft.IdentityModel.Logging;
+#nullable enable
namespace Microsoft.IdentityModel.Tokens
{
+ ///
+ /// Definition for delegate that will validate the audiences value in a token.
+ ///
+ /// The audiences to validate.
+ /// The that is being validated.
+ /// required for validation.
+ ///
+ /// A that contains the results of validating the issuer.
+ /// This delegate is not expected to throw.
+ internal delegate AudienceValidationResult ValidateAudience(
+ IEnumerable audiences,
+ SecurityToken? securityToken,
+ TokenValidationParameters validationParameters,
+ CallContext callContext);
+
///
/// Partial class for Audience Validation.
///
@@ -62,17 +79,10 @@ public static void ValidateAudience(IEnumerable audiences, SecurityToken
new SecurityTokenInvalidAudienceException(LogHelper.FormatInvariant(LogMessages.IDX10206))
{ InvalidAudience = Utility.SerializeAsSingleCommaDelimitedString(audiences) });
- // create enumeration of all valid audiences from validationParameters
- IEnumerable validationParametersAudiences;
-
- if (validationParameters.ValidAudiences == null)
- validationParametersAudiences = new[] { validationParameters.ValidAudience };
- else if (string.IsNullOrWhiteSpace(validationParameters.ValidAudience))
- validationParametersAudiences = validationParameters.ValidAudiences;
- else
- validationParametersAudiences = validationParameters.ValidAudiences.Concat(new[] { validationParameters.ValidAudience });
+ if (audiences is not List audiencesAsList)
+ audiencesAsList = audiences.ToList();
- if (AudienceIsValid(audiences, validationParameters, validationParametersAudiences))
+ if (AudienceIsValid(audiencesAsList, validationParameters))
return;
SecurityTokenInvalidAudienceException ex = new SecurityTokenInvalidAudienceException(
@@ -88,39 +98,168 @@ public static void ValidateAudience(IEnumerable audiences, SecurityToken
throw LogHelper.LogExceptionMessage(ex);
}
- private static bool AudienceIsValid(IEnumerable audiences, TokenValidationParameters validationParameters, IEnumerable validationParametersAudiences)
+
+ ///
+ /// Determines if the audiences found in a are valid.
+ ///
+ /// The audiences found in the .
+ /// The being validated.
+ /// required for validation.
+ ///
+ /// If 'validationParameters' is null.
+ /// If 'audiences' is null and is true.
+ /// If is null or whitespace and is null.
+ /// If none of the 'audiences' matched either or one of .
+ /// An EXACT match is required.
+#pragma warning disable CA1801 // TODO: remove pragma disable once callContext is used for logging
+ internal static AudienceValidationResult ValidateAudience(IEnumerable audiences, SecurityToken? securityToken, TokenValidationParameters validationParameters, CallContext callContext)
+#pragma warning restore CA1801
{
- foreach (string tokenAudience in audiences)
+ if (validationParameters == null)
+ return new AudienceValidationResult(
+ Utility.SerializeAsSingleCommaDelimitedString(audiences),
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10000,
+ LogHelper.MarkAsNonPII(nameof(validationParameters))),
+ typeof(ArgumentNullException),
+ new StackFrame(true)));
+
+ if (!validationParameters.ValidateAudience)
{
+ LogHelper.LogWarning(LogMessages.IDX10233);
+ return new AudienceValidationResult(Utility.SerializeAsSingleCommaDelimitedString(audiences));
+ }
+
+ if (audiences == null)
+ return new AudienceValidationResult(
+ Utility.SerializeAsSingleCommaDelimitedString(audiences),
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10207,
+ null),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true)));
+
+ if (string.IsNullOrWhiteSpace(validationParameters.ValidAudience) && (validationParameters.ValidAudiences == null))
+ return new AudienceValidationResult(
+ Utility.SerializeAsSingleCommaDelimitedString(audiences),
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10208,
+ null),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true)));
+
+ if (audiences is not List audiencesAsList)
+ audiencesAsList = audiences.ToList();
+
+ if (audiencesAsList.Count == 0)
+ return new AudienceValidationResult(
+ Utility.SerializeAsSingleCommaDelimitedString(audiencesAsList),
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10206,
+ null),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true)));
+
+ string? validAudience = AudienceIsValidReturning(audiencesAsList, validationParameters);
+ if (validAudience != null)
+ {
+ return new AudienceValidationResult(validAudience);
+ }
+
+ return new AudienceValidationResult(
+ Utility.SerializeAsSingleCommaDelimitedString(audiencesAsList),
+ ValidationFailureType.AudienceValidationFailed,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(audiencesAsList)),
+ LogHelper.MarkAsNonPII(validationParameters.ValidAudience ?? "null"),
+ LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidAudiences))),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true)));
+ }
+
+ private static bool AudienceIsValid(List audiences, TokenValidationParameters validationParameters)
+ {
+ return AudienceIsValidReturning(audiences, validationParameters) != null;
+ }
+
+ private static string? AudienceIsValidReturning(List audiences, TokenValidationParameters validationParameters)
+ {
+ string? validAudience = null;
+ if (!string.IsNullOrWhiteSpace(validationParameters.ValidAudience))
+ validAudience = AudiencesMatchSingle(audiences, validationParameters.ValidAudience, validationParameters.IgnoreTrailingSlashWhenValidatingAudience);
+
+ if (validAudience == null && validationParameters.ValidAudiences != null)
+ {
+ if (validationParameters.ValidAudiences is not List validAudiences)
+ validAudiences = validationParameters.ValidAudiences.ToList();
+
+ validAudience = AudiencesMatchList(audiences, validAudiences, validationParameters.IgnoreTrailingSlashWhenValidatingAudience);
+ }
+
+ return validAudience;
+ }
+
+ private static string? AudiencesMatchSingle(List audiences, string validAudience, bool ignoreTrailingSlashWhenValidatingAudience)
+ {
+ for (int i = 0; i < audiences.Count; i++)
+ {
+ string tokenAudience = audiences[i];
if (string.IsNullOrWhiteSpace(tokenAudience))
continue;
- foreach (string validAudience in validationParametersAudiences)
+ if (AudiencesMatch(ignoreTrailingSlashWhenValidatingAudience, tokenAudience, validAudience))
{
- if (string.IsNullOrWhiteSpace(validAudience))
+ if (LogHelper.IsEnabled(EventLogLevel.Informational))
+ LogHelper.LogInformation(LogMessages.IDX10234, LogHelper.MarkAsNonPII(tokenAudience));
+
+ return tokenAudience;
+ }
+ }
+
+ return null;
+ }
+
+ private static string? AudiencesMatchList(IList audiences, List validAudiences, bool ignoreTrailingSlashWhenValidatingAudience)
+ {
+ for (int i = 0; i < audiences.Count; i++)
+ {
+ string tokenAudience = audiences[i];
+ if (string.IsNullOrWhiteSpace(tokenAudience))
+ continue;
+
+ foreach (string validAudience in validAudiences)
+ {
+ if (string.IsNullOrEmpty(validAudience))
continue;
- if (AudiencesMatch(validationParameters, tokenAudience, validAudience))
+ if (AudiencesMatch(ignoreTrailingSlashWhenValidatingAudience, tokenAudience, validAudience))
{
if (LogHelper.IsEnabled(EventLogLevel.Informational))
LogHelper.LogInformation(LogMessages.IDX10234, LogHelper.MarkAsNonPII(tokenAudience));
- return true;
+ return tokenAudience;
}
}
}
- return false;
+ return null;
}
- private static bool AudiencesMatch(TokenValidationParameters validationParameters, string tokenAudience, string validAudience)
+ private static bool AudiencesMatch(bool ignoreTrailingSlashWhenValidatingAudience, string tokenAudience, string validAudience)
{
if (validAudience.Length == tokenAudience.Length)
- {
- if (string.Equals(validAudience, tokenAudience))
- return true;
- }
- else if (validationParameters.IgnoreTrailingSlashWhenValidatingAudience && AudiencesMatchIgnoringTrailingSlash(tokenAudience, validAudience))
+ return string.Equals(validAudience, tokenAudience);
+ else if (ignoreTrailingSlashWhenValidatingAudience && AudiencesMatchIgnoringTrailingSlash(tokenAudience, validAudience))
return true;
return false;
@@ -152,3 +291,4 @@ private static bool AudiencesMatchIgnoringTrailingSlash(string tokenAudience, st
}
}
+#nullable disable
diff --git a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs
index 4308d48d77..7fda27db7f 100644
--- a/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs
+++ b/src/System.IdentityModel.Tokens.Jwt/JwtSecurityTokenHandler.cs
@@ -1639,7 +1639,7 @@ protected virtual string CreateActorValue(ClaimsIdentity actor)
/// The audiences found in the .
/// The being validated.
/// required for validation.
- /// See for additional details.
+ /// See for additional details.
protected virtual void ValidateAudience(IEnumerable audiences, JwtSecurityToken jwtToken, TokenValidationParameters validationParameters)
{
Validators.ValidateAudience(audiences, jwtToken, validationParameters);
diff --git a/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs b/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs
index 7ef5e4fa3e..c8caa5156d 100644
--- a/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs
+++ b/test/Microsoft.IdentityModel.TestUtils/IdentityComparer.cs
@@ -574,6 +574,9 @@ internal static bool AreIssuerValidationResultsEqual(
if (issuerValidationResult1.Issuer != issuerValidationResult2.Issuer)
localContext.Diffs.Add($"IssuerValidationResult1.Issuer: {issuerValidationResult1.Issuer} != IssuerValidationResult2.Issuer: {issuerValidationResult2.Issuer}");
+ if (issuerValidationResult1.IsValid != issuerValidationResult2.IsValid)
+ localContext.Diffs.Add($"IssuerValidationResult1.IsValid: {issuerValidationResult1.IsValid} != IssuerValidationResult2.IsValid: {issuerValidationResult2.IsValid}");
+
if (issuerValidationResult1.Source != issuerValidationResult2.Source)
localContext.Diffs.Add($"IssuerValidationResult1.Source: {issuerValidationResult1.Source} != IssuerValidationResult2.Source: {issuerValidationResult2.Source}");
@@ -607,6 +610,69 @@ internal static bool AreIssuerValidationResultsEqual(
return context.Merge(localContext);
}
+ public static bool AreAudienceValidationResultsEqual(object object1, object object2, CompareContext context)
+ {
+ var localContext = new CompareContext(context);
+ if (!ContinueCheckingEquality(object1, object2, context))
+ return context.Merge(localContext);
+
+ return AreAudienceValidationResultsEqual(
+ object1 as AudienceValidationResult,
+ object2 as AudienceValidationResult,
+ "AudienceValidationResult1",
+ "AudienceValidationResult2",
+ null,
+ context);
+ }
+
+ internal static bool AreAudienceValidationResultsEqual(
+ AudienceValidationResult audienceValidationResult1,
+ AudienceValidationResult audienceValidationResult2,
+ string name1,
+ string name2,
+ string stackPrefix,
+ CompareContext context)
+ {
+ var localContext = new CompareContext(context);
+ if (!ContinueCheckingEquality(audienceValidationResult1, audienceValidationResult2, localContext))
+ return context.Merge(localContext);
+
+ if (audienceValidationResult1.Audience != audienceValidationResult2.Audience)
+ localContext.Diffs.Add($"AudienceValidationResult1.Audience: '{audienceValidationResult1.Audience}' != AudienceValidationResult2.Audience: '{audienceValidationResult2.Audience}'");
+
+ if (audienceValidationResult1.IsValid != audienceValidationResult2.IsValid)
+ localContext.Diffs.Add($"AudienceValidationResult1.IsValid: {audienceValidationResult1.IsValid} != AudienceValidationResult2.IsValid: {audienceValidationResult2.IsValid}");
+
+ // true => both are not null.
+ if (ContinueCheckingEquality(audienceValidationResult1.Exception, audienceValidationResult2.Exception, localContext))
+ {
+ AreStringsEqual(
+ audienceValidationResult1.Exception.Message,
+ audienceValidationResult2.Exception.Message,
+ $"({name1})audienceValidationResult1.Exception.Message",
+ $"({name2})audienceValidationResult2.Exception.Message",
+ localContext);
+
+ AreStringsEqual(
+ audienceValidationResult1.Exception.Source,
+ audienceValidationResult2.Exception.Source,
+ $"({name1})audienceValidationResult1.Exception.Source",
+ $"({name2})audienceValidationResult2.Exception.Source",
+ localContext);
+
+ if (!string.IsNullOrEmpty(stackPrefix))
+ AreStringPrefixesEqual(
+ audienceValidationResult1.Exception.StackTrace.Trim(),
+ audienceValidationResult2.Exception.StackTrace.Trim(),
+ $"({name1})audienceValidationResult1.Exception.StackTrace",
+ $"({name2})audienceValidationResult2.Exception.StackTrace",
+ stackPrefix.Trim(),
+ localContext);
+ }
+
+ return context.Merge(localContext);
+ }
+
public static bool AreJArraysEqual(object object1, object object2, CompareContext context)
{
var localContext = new CompareContext(context);
diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/AudienceValidationResultTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/AudienceValidationResultTests.cs
new file mode 100644
index 0000000000..aa6a45b784
--- /dev/null
+++ b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/AudienceValidationResultTests.cs
@@ -0,0 +1,597 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics;
+using Microsoft.IdentityModel.JsonWebTokens;
+using Microsoft.IdentityModel.Logging;
+using Microsoft.IdentityModel.TestUtils;
+using Microsoft.IdentityModel.Tokens.Json.Tests;
+using Xunit;
+using System.Collections.Generic;
+
+namespace Microsoft.IdentityModel.Tokens.Validation.Tests
+{
+ public class AudienceValidationResultTests
+ {
+ [Theory, MemberData(nameof(ValidateAudienceParametersTestCases), DisableDiscoveryEnumeration = true)]
+ public void ValidateAudienceParameters(AudienceValidationTheoryData theoryData)
+ {
+ CompareContext context = TestUtilities.WriteHeader($"{this}.AudienceValidatorResultTests", theoryData);
+
+ AudienceValidationResult audienceValidationResult = Validators.ValidateAudience(
+ theoryData.Audiences,
+ theoryData.SecurityToken,
+ theoryData.ValidationParameters,
+ new CallContext());
+
+ if (audienceValidationResult.Exception == null)
+ theoryData.ExpectedException.ProcessNoException();
+ else
+ theoryData.ExpectedException.ProcessException(audienceValidationResult.Exception, context);
+
+ IdentityComparer.AreAudienceValidationResultsEqual(
+ audienceValidationResult,
+ theoryData.AudienceValidationResult,
+ context);
+
+ TestUtilities.AssertFailIfErrors(context);
+ }
+
+ public static TheoryData ValidateAudienceParametersTestCases
+ {
+ get
+ {
+ return new TheoryData
+ {
+ new AudienceValidationTheoryData
+ {
+ Audiences = new List { "audience1" },
+ ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"),
+ TestId = "ValidationParametersNull",
+ ValidationParameters = null,
+ AudienceValidationResult = new AudienceValidationResult(
+ "audience1",
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10000,
+ LogHelper.MarkAsNonPII("validationParameters")),
+ typeof(ArgumentNullException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = new List { "" },
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "AudiencesEmptyString",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = "audience"},
+ AudienceValidationResult = new AudienceValidationResult(
+ "",
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(""),
+ LogHelper.MarkAsNonPII("audience"),
+ LogHelper.MarkAsNonPII("null")),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = new List { " " },
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "AudiencesWhiteSpace",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = "audience"},
+ AudienceValidationResult = new AudienceValidationResult(
+ " ",
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(" "),
+ LogHelper.MarkAsNonPII("audience"),
+ LogHelper.MarkAsNonPII("null")),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = null,
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10207:"),
+ TestId = "AudiencesNull",
+ AudienceValidationResult = new AudienceValidationResult(
+ "null",
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10207,
+ null),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = new List{ },
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10206:"),
+ TestId = "AudiencesEmptyList",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = "audience"},
+ AudienceValidationResult = new AudienceValidationResult(
+ "empty",
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10206,
+ null),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = new List{ },
+ TestId = "ValidateAudienceFalseAudiencesEmptyList",
+ ValidationParameters = new TokenValidationParameters{ ValidateAudience = false },
+ AudienceValidationResult = new AudienceValidationResult("empty")
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = null,
+ TestId = "ValidateAudienceFalseAudiencesNull",
+ ValidationParameters = new TokenValidationParameters{ ValidateAudience = false },
+ AudienceValidationResult = new AudienceValidationResult("null")
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = new List { "audience1" },
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10208:"),
+ TestId = "ValidAudienceEmptyString",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = "" },
+ AudienceValidationResult = new AudienceValidationResult(
+ "audience1",
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10208,
+ null),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = new List { "audience1" },
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10208:"),
+ TestId = "ValidAudienceWhiteSpace",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = " " },
+ AudienceValidationResult = new AudienceValidationResult(
+ "audience1",
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10208,
+ null),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = new List { "audience1" },
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "ValidAudiencesEmptyString",
+ ValidationParameters = new TokenValidationParameters{ ValidAudiences = new List{ "" } },
+ AudienceValidationResult = new AudienceValidationResult(
+ "audience1",
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII("audience1"),
+ LogHelper.MarkAsNonPII("null"),
+ LogHelper.MarkAsNonPII("")),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = new List { "audience1" },
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "ValidAudiencesWhiteSpace",
+ ValidationParameters = new TokenValidationParameters{ ValidAudiences = new List{ " " } },
+ AudienceValidationResult = new AudienceValidationResult(
+ "audience1",
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII("audience1"),
+ LogHelper.MarkAsNonPII("null"),
+ LogHelper.MarkAsNonPII(" ")),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = new List { "audience1" },
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10208:"),
+ TestId = "ValidateAudienceTrueValidAudienceAndValidAudiencesNull",
+ AudienceValidationResult = new AudienceValidationResult(
+ "audience1",
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10208,
+ null),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ }
+ };
+ }
+ }
+
+ [Theory, MemberData(nameof(ValidateAudienceTheoryData))]
+ public void ValidateAudience(AudienceValidationTheoryData theoryData)
+ {
+ var context = TestUtilities.WriteHeader($"{this}.ValidateAudience", theoryData);
+ AudienceValidationResult audienceValidationResult = Validators.ValidateAudience(
+ theoryData.Audiences,
+ theoryData.SecurityToken,
+ theoryData.ValidationParameters,
+ new CallContext());
+
+ if (audienceValidationResult.Exception != null)
+ theoryData.ExpectedException.ProcessException(audienceValidationResult.Exception);
+ else
+ theoryData.ExpectedException.ProcessNoException(context);
+
+ IdentityComparer.AreAudienceValidationResultsEqual(
+ audienceValidationResult,
+ theoryData.AudienceValidationResult,
+ context);
+
+ TestUtilities.AssertFailIfErrors(context);
+ }
+
+ public static TheoryData ValidateAudienceTheoryData
+ {
+ get
+ {
+ var audience1 = "http://audience1.com";
+ var audience2 = "http://audience2.com";
+ List audiences1 = new List { "", audience1 };
+ List audiences1WithSlash = new List { "", audience1 + "/" };
+ List audiences1WithTwoSlashes = new List { "", audience1 + "//" };
+ List audiences2 = new List { "", audience2 };
+ List audiences2WithSlash = new List { "", audience2 + "/" };
+
+ var commaAudience1 = ", " + audience1;
+ var commaAudience2 = ", " + audience2;
+ var audience1Slash = audience1 + "/";
+ var audience2Slash = audience2 + "/";
+ var commaAudience1Slash = commaAudience1 + "/";
+ var commaAudience2Slash = commaAudience2 + "/";
+
+ return new TheoryData
+ {
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1,
+ TestId = "SameLengthMatched",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = audience1 },
+ SecurityToken = JsonUtilities.CreateUnsignedJsonWebToken(JwtRegisteredClaimNames.Iss, "Issuer"),
+ AudienceValidationResult = new AudienceValidationResult(audience1)
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1,
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "SameLengthNotMatched",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = audience2 },
+ SecurityToken = JsonUtilities.CreateUnsignedJsonWebToken(JwtRegisteredClaimNames.Iss, "Issuer"),
+ AudienceValidationResult = new AudienceValidationResult(
+ commaAudience1,
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(commaAudience1),
+ LogHelper.MarkAsNonPII(audience2),
+ LogHelper.MarkAsNonPII("null")),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1,
+ TestId = "NoMatchTVPValidateFalse",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = audience2, ValidateAudience = false },
+ SecurityToken = JsonUtilities.CreateUnsignedJsonWebToken(JwtRegisteredClaimNames.Iss, "Issuer"),
+ AudienceValidationResult = new AudienceValidationResult(commaAudience1)
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1,
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "AudiencesValidAudienceWithSlashNotMatched",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = audience2 + "/" },
+ SecurityToken = JsonUtilities.CreateUnsignedJsonWebToken(JwtRegisteredClaimNames.Iss, "Issuer"),
+ AudienceValidationResult = new AudienceValidationResult(
+ commaAudience1,
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(commaAudience1),
+ LogHelper.MarkAsNonPII(audience2Slash),
+ LogHelper.MarkAsNonPII("null")),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences2WithSlash,
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "AudiencesWithSlashValidAudienceSameLengthNotMatched",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = audience1 },
+ AudienceValidationResult = new AudienceValidationResult(
+ commaAudience2Slash,
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(commaAudience2Slash),
+ LogHelper.MarkAsNonPII(audience1),
+ LogHelper.MarkAsNonPII("null")),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1,
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "ValidAudienceWithSlashTVPFalse",
+ ValidationParameters = new TokenValidationParameters{ IgnoreTrailingSlashWhenValidatingAudience = false, ValidAudience = audience1 + "/" },
+ AudienceValidationResult = new AudienceValidationResult(
+ commaAudience1,
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(commaAudience1),
+ LogHelper.MarkAsNonPII(audience1Slash),
+ LogHelper.MarkAsNonPII("null")),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1,
+ TestId = "ValidAudienceWithSlashTVPTrue",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = audience1 + "/" },
+ AudienceValidationResult = new AudienceValidationResult(audience1)
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1,
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "ValidAudiencesWithSlashTVPFalse",
+ ValidationParameters = new TokenValidationParameters{ IgnoreTrailingSlashWhenValidatingAudience = false, ValidAudiences = audiences1WithSlash },
+ AudienceValidationResult = new AudienceValidationResult(
+ commaAudience1,
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(commaAudience1),
+ LogHelper.MarkAsNonPII("null"),
+ LogHelper.MarkAsNonPII(commaAudience1Slash)),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1,
+ TestId = "ValidAudiencesWithSlashTVPTrue",
+ ValidationParameters = new TokenValidationParameters{ ValidAudiences = audiences1WithSlash },
+ AudienceValidationResult = new AudienceValidationResult(audience1)
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1,
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "ValidAudienceWithExtraChar",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = audience1 + "A" },
+ AudienceValidationResult = new AudienceValidationResult(
+ commaAudience1,
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(commaAudience1),
+ LogHelper.MarkAsNonPII(audience1 + "A"),
+ LogHelper.MarkAsNonPII("null")),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1,
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "ValidAudienceWithDoubleSlashTVPTrue",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = audience1 + "//" },
+ AudienceValidationResult = new AudienceValidationResult(
+ commaAudience1,
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(commaAudience1),
+ LogHelper.MarkAsNonPII(audience1 + "//"),
+ LogHelper.MarkAsNonPII("null")),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1,
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "ValidAudiencesWithDoubleSlashTVPTrue",
+ ValidationParameters = new TokenValidationParameters{ ValidAudiences = audiences1WithTwoSlashes },
+ AudienceValidationResult = new AudienceValidationResult(
+ commaAudience1,
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(commaAudience1),
+ LogHelper.MarkAsNonPII("null"),
+ LogHelper.MarkAsNonPII(commaAudience1 + "//")),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1WithSlash,
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "TokenAudienceWithSlashTVPFalse",
+ ValidationParameters = new TokenValidationParameters{ IgnoreTrailingSlashWhenValidatingAudience = false, ValidAudience = audience1 },
+ AudienceValidationResult = new AudienceValidationResult(
+ commaAudience1Slash,
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(commaAudience1Slash),
+ LogHelper.MarkAsNonPII(audience1),
+ LogHelper.MarkAsNonPII("null")),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1WithSlash,
+ TestId = "TokenAudienceWithSlashTVPTrue",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = audience1 },
+ AudienceValidationResult = new AudienceValidationResult(audience1Slash)
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences2WithSlash,
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "TokenAudienceWithSlashNotEqual",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = audience1 },
+ AudienceValidationResult = new AudienceValidationResult(
+ commaAudience2Slash,
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(commaAudience2Slash),
+ LogHelper.MarkAsNonPII(audience1),
+ LogHelper.MarkAsNonPII("null")),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1WithSlash,
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "TokenAudiencesWithSlashTVPFalse",
+ ValidationParameters = new TokenValidationParameters{ IgnoreTrailingSlashWhenValidatingAudience = false, ValidAudience = audience1 },
+ AudienceValidationResult = new AudienceValidationResult(
+ commaAudience1Slash,
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(commaAudience1Slash),
+ LogHelper.MarkAsNonPII(audience1),
+ LogHelper.MarkAsNonPII("null")),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1WithSlash,
+ TestId = "TokenAudiencesWithSlashTVPTrue",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = audience1 },
+ AudienceValidationResult = new AudienceValidationResult(audience1Slash)
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1WithSlash,
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "TokenAudiencesWithSlashValidAudiencesNotMatchedTVPTrue",
+ ValidationParameters = new TokenValidationParameters{ ValidAudiences = audiences2 },
+ AudienceValidationResult = new AudienceValidationResult(
+ commaAudience1Slash,
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(commaAudience1Slash),
+ LogHelper.MarkAsNonPII("null"),
+ LogHelper.MarkAsNonPII(commaAudience2)),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ },
+ new AudienceValidationTheoryData
+ {
+ Audiences = audiences1WithTwoSlashes,
+ ExpectedException = ExpectedException.SecurityTokenInvalidAudienceException("IDX10214:"),
+ TestId = "TokenAudienceWithTwoSlashesTVPTrue",
+ ValidationParameters = new TokenValidationParameters{ ValidAudience = audience1 },
+ AudienceValidationResult = new AudienceValidationResult(
+ commaAudience1 + "//",
+ ValidationFailureType.NullArgument,
+ new ExceptionDetail(
+ new MessageDetail(
+ LogMessages.IDX10214,
+ LogHelper.MarkAsNonPII(commaAudience1 + "//"),
+ LogHelper.MarkAsNonPII(audience1),
+ LogHelper.MarkAsNonPII("null")),
+ typeof(SecurityTokenInvalidAudienceException),
+ new StackFrame(true),
+ null)),
+ }
+ };
+ }
+ }
+
+ public class AudienceValidationTheoryData : TheoryDataBase
+ {
+ public List Audiences { get; set; }
+
+ internal AudienceValidationResult AudienceValidationResult { get; set; }
+
+ public SecurityToken SecurityToken { get; set; }
+
+ public TokenValidationParameters ValidationParameters { get; set; } = new TokenValidationParameters();
+
+ internal ValidationFailureType ValidationFailureType { get; set; }
+ }
+
+
+ }
+}
diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/IssuerValidationResultTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/IssuerValidationResultTests.cs
index c8b5694cc6..51ad101026 100644
--- a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/IssuerValidationResultTests.cs
+++ b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/IssuerValidationResultTests.cs
@@ -32,6 +32,8 @@ public async Task IssuerValidatorAsyncTests(IssuerValidationResultsTheoryData th
if (issuerValidationResult.Exception != null)
theoryData.ExpectedException.ProcessException(issuerValidationResult.Exception, context);
+ else
+ theoryData.ExpectedException.ProcessNoException();
IdentityComparer.AreIssuerValidationResultsEqual(
issuerValidationResult,