Skip to content

Commit c6f83c4

Browse files
FuPingFrancoFranco FungiNinja
authored
Remove Delegate Checks Audience Validator and Prevents Null Setting of Delegate (#2758)
* Re-factor audience validator to use ValidatationParameters. * Addressing feedback * Address PR feedback. (#2765) * Addressed PR feedback. Updated tests * Removed exception from documentation * Updated TokenType tests --------- Co-authored-by: Ignacio Inglese <[email protected]> --------- Co-authored-by: Franco Fung <[email protected]> Co-authored-by: Ignacio Inglese <[email protected]>
1 parent 23a0b12 commit c6f83c4

File tree

6 files changed

+169
-341
lines changed

6 files changed

+169
-341
lines changed

src/Microsoft.IdentityModel.Tokens/LogMessages.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ internal static class LogMessages
3737
public const string IDX10209 = "IDX10209: Token has length: '{0}' which is larger than the MaximumTokenSizeInBytes: '{1}'.";
3838
public const string IDX10211 = "IDX10211: Unable to validate issuer. The 'issuer' parameter is null or whitespace.";
3939
public const string IDX10214 = "IDX10214: Audience validation failed. Audiences: '{0}'. Did not match: validationParameters.ValidAudience: '{1}' or validationParameters.ValidAudiences: '{2}'.";
40+
public const string IDX10215 = "IDX10215: Audience validation failed. Audiences: '{0}'. Did not match: validationParameters.ValidAudiences: '{1}'.";
4041
public const string IDX10222 = "IDX10222: Lifetime validation failed. The token is not yet valid. ValidFrom (UTC): '{0}', Current time (UTC): '{1}'.";
4142
public const string IDX10223 = "IDX10223: Lifetime validation failed. The token is expired. ValidTo (UTC): '{0}', Current time (UTC): '{1}'.";
4243
public const string IDX10224 = "IDX10224: Lifetime validation failed. The NotBefore (UTC): '{0}' is after Expires (UTC): '{1}'.";

src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ internal class ValidationParameters
2121
private string _nameClaimType = ClaimsIdentity.DefaultNameClaimType;
2222
private string _roleClaimType = ClaimsIdentity.DefaultRoleClaimType;
2323
private Dictionary<string, object> _instancePropertyBag;
24-
private IList<string> _validTokenTypes = [];
24+
private IList<string> _validTokenTypes;
25+
private IList<string> _validAudiences;
2526

2627
private AlgorithmValidatorDelegate _algorithmValidator = Validators.ValidateAlgorithm;
2728
private AudienceValidatorDelegate _audienceValidator = Validators.ValidateAudience;
@@ -89,9 +90,9 @@ protected ValidationParameters(ValidationParameters other)
8990
ValidateSignatureLast = other.ValidateSignatureLast;
9091
ValidateWithLKG = other.ValidateWithLKG;
9192
ValidAlgorithms = other.ValidAlgorithms;
92-
ValidAudiences = other.ValidAudiences;
93+
_validAudiences = other.ValidAudiences;
9394
ValidIssuers = other.ValidIssuers;
94-
ValidTypes = other.ValidTypes;
95+
_validTokenTypes = other.ValidTypes;
9596
}
9697

9798
/// <summary>
@@ -513,9 +514,12 @@ public TypeValidatorDelegate TypeValidator
513514

514515
/// <summary>
515516
/// Gets the <see cref="IList{String}"/> that contains valid audiences that will be used to check against the token's audience.
516-
/// The default is <c>null</c>.
517+
/// The default is an empty collection.
517518
/// </summary>
518-
public IList<string> ValidAudiences { get; }
519+
public IList<string> ValidAudiences =>
520+
_validAudiences ??
521+
Interlocked.CompareExchange(ref _validAudiences, [], null) ??
522+
_validAudiences;
519523

520524
/// <summary>
521525
/// Gets the <see cref="IList{String}"/> that contains valid issuers that will be used to check against the token's issuer.
@@ -529,19 +533,11 @@ public TypeValidatorDelegate TypeValidator
529533
/// In the case of a JWE, this property will ONLY apply to the inner token header.
530534
/// The default is an empty collection.
531535
/// </summary>
532-
/// <exception cref="ArgumentNullException">Thrown when the value is set as null.</exception>
533536
/// <returns>The <see cref="IList{String}"/> that contains valid token types that will be used to check against the token's 'typ' claim.</returns>
534-
public IList<string> ValidTypes
535-
{
536-
get
537-
{
538-
return _validTokenTypes;
539-
}
540-
set
541-
{
542-
_validTokenTypes = value ?? throw new ArgumentNullException(nameof(value));
543-
}
544-
}
537+
public IList<string> ValidTypes =>
538+
_validTokenTypes ??
539+
Interlocked.CompareExchange(ref _validTokenTypes, [], null) ??
540+
_validTokenTypes;
545541

546542
public bool ValidateActor { get; set; }
547543
}

src/Microsoft.IdentityModel.Tokens/Validation/Validators.Audience.cs

Lines changed: 24 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ namespace Microsoft.IdentityModel.Tokens
2121
/// <returns>A <see cref="IssuerValidationResult"/>that contains the results of validating the issuer.</returns>
2222
/// <remarks>This delegate is not expected to throw.</remarks>
2323
internal delegate AudienceValidationResult AudienceValidatorDelegate(
24-
IEnumerable<string> audiences,
24+
IList<string> audiences,
2525
SecurityToken? securityToken,
26-
TokenValidationParameters validationParameters,
26+
ValidationParameters validationParameters,
2727
CallContext callContext);
2828

2929
/// <summary>
@@ -34,7 +34,7 @@ public static partial class Validators
3434
/// <summary>
3535
/// Determines if the audiences found in a <see cref="SecurityToken"/> are valid.
3636
/// </summary>
37-
/// <param name="audiences">The audiences found in the <see cref="SecurityToken"/>.</param>
37+
/// <param name="tokenAudiences">The audiences found in the <see cref="SecurityToken"/>.</param>
3838
/// <param name="securityToken">The <see cref="SecurityToken"/> being validated.</param>
3939
/// <param name="validationParameters">The <see cref="TokenValidationParameters"/> to be used for validating the token.</param>
4040
/// <param name="callContext"></param>
@@ -44,12 +44,12 @@ public static partial class Validators
4444
/// <exception cref="SecurityTokenInvalidAudienceException">If none of the 'audiences' matched either <see cref="TokenValidationParameters.ValidAudience"/> or one of <see cref="TokenValidationParameters.ValidAudiences"/>.</exception>
4545
/// <remarks>An EXACT match is required.</remarks>
4646
#pragma warning disable CA1801 // TODO: remove pragma disable once callContext is used for logging
47-
internal static AudienceValidationResult ValidateAudience(IEnumerable<string> audiences, SecurityToken? securityToken, TokenValidationParameters validationParameters, CallContext callContext)
47+
internal static AudienceValidationResult ValidateAudience(IList<string> tokenAudiences, SecurityToken? securityToken, ValidationParameters validationParameters, CallContext callContext)
4848
#pragma warning restore CA1801
4949
{
5050
if (validationParameters == null)
5151
return new AudienceValidationResult(
52-
Utility.SerializeAsSingleCommaDelimitedString(audiences),
52+
Utility.SerializeAsSingleCommaDelimitedString(tokenAudiences),
5353
ValidationFailureType.NullArgument,
5454
new ExceptionDetail(
5555
new MessageDetail(
@@ -58,15 +58,9 @@ internal static AudienceValidationResult ValidateAudience(IEnumerable<string> au
5858
typeof(ArgumentNullException),
5959
new StackFrame(true)));
6060

61-
if (!validationParameters.ValidateAudience)
62-
{
63-
LogHelper.LogWarning(LogMessages.IDX10233);
64-
return new AudienceValidationResult(Utility.SerializeAsSingleCommaDelimitedString(audiences));
65-
}
66-
67-
if (audiences == null)
61+
if (tokenAudiences == null)
6862
return new AudienceValidationResult(
69-
Utility.SerializeAsSingleCommaDelimitedString(audiences),
63+
Utility.SerializeAsSingleCommaDelimitedString(tokenAudiences),
7064
ValidationFailureType.NullArgument,
7165
new ExceptionDetail(
7266
new MessageDetail(
@@ -75,23 +69,9 @@ internal static AudienceValidationResult ValidateAudience(IEnumerable<string> au
7569
typeof(SecurityTokenInvalidAudienceException),
7670
new StackFrame(true)));
7771

78-
if (string.IsNullOrWhiteSpace(validationParameters.ValidAudience) && (validationParameters.ValidAudiences == null))
72+
if (tokenAudiences.Count == 0)
7973
return new AudienceValidationResult(
80-
Utility.SerializeAsSingleCommaDelimitedString(audiences),
81-
ValidationFailureType.NullArgument,
82-
new ExceptionDetail(
83-
new MessageDetail(
84-
LogMessages.IDX10208,
85-
null),
86-
typeof(SecurityTokenInvalidAudienceException),
87-
new StackFrame(true)));
88-
89-
if (audiences is not List<string> audiencesAsList)
90-
audiencesAsList = audiences.ToList();
91-
92-
if (audiencesAsList.Count == 0)
93-
return new AudienceValidationResult(
94-
Utility.SerializeAsSingleCommaDelimitedString(audiencesAsList),
74+
Utility.SerializeAsSingleCommaDelimitedString(tokenAudiences),
9575
ValidationFailureType.NullArgument,
9676
new ExceptionDetail(
9777
new MessageDetail(
@@ -100,81 +80,37 @@ internal static AudienceValidationResult ValidateAudience(IEnumerable<string> au
10080
typeof(SecurityTokenInvalidAudienceException),
10181
new StackFrame(true)));
10282

103-
string? validAudience = AudienceIsValidReturning(audiencesAsList, validationParameters);
83+
string? validAudience = ValidTokenAudience(tokenAudiences, validationParameters.ValidAudiences, validationParameters.IgnoreTrailingSlashWhenValidatingAudience);
10484
if (validAudience != null)
105-
{
10685
return new AudienceValidationResult(validAudience);
107-
}
10886

10987
return new AudienceValidationResult(
110-
Utility.SerializeAsSingleCommaDelimitedString(audiencesAsList),
88+
Utility.SerializeAsSingleCommaDelimitedString(tokenAudiences),
11189
ValidationFailureType.AudienceValidationFailed,
11290
new ExceptionDetail(
11391
new MessageDetail(
114-
LogMessages.IDX10214,
115-
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(audiencesAsList)),
116-
LogHelper.MarkAsNonPII(validationParameters.ValidAudience ?? "null"),
92+
LogMessages.IDX10215,
93+
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(tokenAudiences)),
11794
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidAudiences))),
11895
typeof(SecurityTokenInvalidAudienceException),
11996
new StackFrame(true)));
12097
}
12198

122-
private static bool AudienceIsValid(List<string> audiences, TokenValidationParameters validationParameters)
123-
{
124-
return AudienceIsValidReturning(audiences, validationParameters) != null;
125-
}
126-
127-
private static string? AudienceIsValidReturning(List<string> audiences, TokenValidationParameters validationParameters)
128-
{
129-
string? validAudience = null;
130-
if (!string.IsNullOrWhiteSpace(validationParameters.ValidAudience))
131-
validAudience = AudiencesMatchSingle(audiences, validationParameters.ValidAudience, validationParameters.IgnoreTrailingSlashWhenValidatingAudience);
132-
133-
if (validAudience == null && validationParameters.ValidAudiences != null)
134-
{
135-
if (validationParameters.ValidAudiences is not List<string> validAudiences)
136-
validAudiences = validationParameters.ValidAudiences.ToList();
137-
138-
validAudience = AudiencesMatchList(audiences, validAudiences, validationParameters.IgnoreTrailingSlashWhenValidatingAudience);
139-
}
140-
141-
return validAudience;
142-
}
143-
144-
private static string? AudiencesMatchSingle(List<string> audiences, string validAudience, bool ignoreTrailingSlashWhenValidatingAudience)
99+
private static string? ValidTokenAudience(IList<string> tokenAudiences, IList<string> validAudiences, bool ignoreTrailingSlashWhenValidatingAudience)
145100
{
146-
for (int i = 0; i < audiences.Count; i++)
101+
for (int i = 0; i < tokenAudiences.Count; i++)
147102
{
148-
string tokenAudience = audiences[i];
149-
if (string.IsNullOrWhiteSpace(tokenAudience))
103+
string tokenAudience = tokenAudiences[i];
104+
if (string.IsNullOrEmpty(tokenAudience))
150105
continue;
151106

152-
if (AudiencesMatch(ignoreTrailingSlashWhenValidatingAudience, tokenAudience, validAudience))
107+
for (int j = 0; j < validAudiences.Count; j++)
153108
{
154-
if (LogHelper.IsEnabled(EventLogLevel.Informational))
155-
LogHelper.LogInformation(LogMessages.IDX10234, LogHelper.MarkAsNonPII(tokenAudience));
156-
157-
return tokenAudience;
158-
}
159-
}
160-
161-
return null;
162-
}
163-
164-
private static string? AudiencesMatchList(IList<string> audiences, List<string> validAudiences, bool ignoreTrailingSlashWhenValidatingAudience)
165-
{
166-
for (int i = 0; i < audiences.Count; i++)
167-
{
168-
string tokenAudience = audiences[i];
169-
if (string.IsNullOrWhiteSpace(tokenAudience))
170-
continue;
171-
172-
foreach (string validAudience in validAudiences)
173-
{
174-
if (string.IsNullOrEmpty(validAudience))
109+
if (string.IsNullOrEmpty(validAudiences[j]))
175110
continue;
176111

177-
if (AudiencesMatch(ignoreTrailingSlashWhenValidatingAudience, tokenAudience, validAudience))
112+
113+
if (AudienceMatches(ignoreTrailingSlashWhenValidatingAudience, tokenAudience, validAudiences[j]))
178114
{
179115
if (LogHelper.IsEnabled(EventLogLevel.Informational))
180116
LogHelper.LogInformation(LogMessages.IDX10234, LogHelper.MarkAsNonPII(tokenAudience));
@@ -187,17 +123,17 @@ private static bool AudienceIsValid(List<string> audiences, TokenValidationParam
187123
return null;
188124
}
189125

190-
private static bool AudiencesMatch(bool ignoreTrailingSlashWhenValidatingAudience, string tokenAudience, string validAudience)
126+
private static bool AudienceMatches(bool ignoreTrailingSlashWhenValidatingAudience, string tokenAudience, string validAudience)
191127
{
192128
if (validAudience.Length == tokenAudience.Length)
193129
return string.Equals(validAudience, tokenAudience);
194-
else if (ignoreTrailingSlashWhenValidatingAudience && NewAudiencesMatchIgnoringTrailingSlash(tokenAudience, validAudience))
130+
else if (ignoreTrailingSlashWhenValidatingAudience && AudienceMatchesIgnoringTrailingSlash(tokenAudience, validAudience))
195131
return true;
196132

197133
return false;
198134
}
199135

200-
private static bool NewAudiencesMatchIgnoringTrailingSlash(string tokenAudience, string validAudience)
136+
private static bool AudienceMatchesIgnoringTrailingSlash(string tokenAudience, string validAudience)
201137
{
202138
int length = -1;
203139

0 commit comments

Comments
 (0)