Skip to content

Commit bb9a6d9

Browse files
FuPingFrancoFranco Fung
andauthored
Remove Delegate Checks in Multiple Validators and Prevents Null Setting of Delegates (#2725)
* Removed delegate checks for token type validation. * Fix un-referenced message check * Added internal delegate for token type validation * Prevent delegates from being null. * Simplify Valid types getter --------- Co-authored-by: Franco Fung <francofung@microsoft.com>
1 parent ac40df6 commit bb9a6d9

11 files changed

Lines changed: 211 additions & 397 deletions

src/Microsoft.IdentityModel.Tokens/LogMessages.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ internal static class LogMessages
7373
public const string IDX10256 = "IDX10256: Unable to validate the token type. TokenValidationParameters.ValidTypes is set, but the 'typ' header claim is null or empty.";
7474
public const string IDX10257 = "IDX10257: Token type validation failed. Type: '{0}'. Did not match: validationParameters.TokenTypes: '{1}'.";
7575
public const string IDX10258 = "IDX10258: Token type validated. Type: '{0}'.";
76-
public const string IDX10259 = "IDX10259: Unable to validate the token type, delegate threw an exception.";
7776
// public const string IDX10260 = "IDX10260:";
7877
public const string IDX10261 = "IDX10261: Unable to retrieve configuration from authority: '{0}'. \nProceeding with token validation in case the relevant properties have been set manually on the TokenValidationParameters. Exception caught: \n {1}. See https://aka.ms/validate-using-configuration-manager for additional information.";
7978
public const string IDX10262 = "IDX10262: One of the issuers in TokenValidationParameters.ValidIssuers was null or an empty string. See https://aka.ms/wilson/tokenvalidation for details.";

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

Lines changed: 89 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.ComponentModel;
77
using System.Security.Claims;
8+
using System.Threading;
89
using Microsoft.IdentityModel.Abstractions;
910
using Microsoft.IdentityModel.Logging;
1011

@@ -20,8 +21,13 @@ internal class ValidationParameters
2021
private string _nameClaimType = ClaimsIdentity.DefaultNameClaimType;
2122
private string _roleClaimType = ClaimsIdentity.DefaultRoleClaimType;
2223
private Dictionary<string, object> _instancePropertyBag;
24+
private IList<string> _validTokenTypes = [];
2325

24-
private IssuerValidationDelegateAsync _issuerValidationDelegate = Validators.ValidateIssuerAsync;
26+
private AudienceValidatorDelegate _audienceValidator = Validators.ValidateAudience;
27+
private IssuerValidationDelegateAsync _issuerValidatorAsync = Validators.ValidateIssuerAsync;
28+
private LifetimeValidatorDelegate _lifetimeValidator = Validators.ValidateLifetime;
29+
private TypeValidatorDelegate _typeValidator = Validators.ValidateTokenType;
30+
private TokenReplayValidatorDelegate _tokenReplayValidator = Validators.ValidateTokenReplay;
2531

2632
/// <summary>
2733
/// This is the default value of <see cref="ClaimsIdentity.AuthenticationType"/> when creating a <see cref="ClaimsIdentity"/>.
@@ -112,14 +118,25 @@ public ValidationParameters()
112118
public AlgorithmValidator AlgorithmValidator { get; set; }
113119

114120
/// <summary>
115-
/// Gets or sets a delegate that will be used to validate the audience.
121+
/// Allows overriding the delegate that will be used to validate the audience.
116122
/// </summary>
117123
/// <remarks>
118-
/// If set, this delegate will be called to validate the 'audience', instead of default processing.
124+
/// If set, this delegate will be responsible for validating the 'audience', instead of default processing.
119125
/// This means that no default 'audience' validation will occur.
120-
/// Even if <see cref="ValidateAudience"/> is false, this delegate will still be called.
121126
/// </remarks>
122-
public AudienceValidator AudienceValidator { get; set; }
127+
/// <exception cref="ArgumentNullException">Thrown when the value is set as null.</exception>
128+
/// <returns>The <see cref="AudienceValidatorDelegate"/> used to validate the issuer of a token</returns>
129+
public AudienceValidatorDelegate AudienceValidator
130+
{
131+
get
132+
{
133+
return _audienceValidator;
134+
}
135+
set
136+
{
137+
_audienceValidator = value ?? throw new ArgumentNullException(nameof(value), "AudienceValidator cannot be set as null.");
138+
}
139+
}
123140

124141
/// <summary>
125142
/// Gets or sets the AuthenticationType when creating a <see cref="ClaimsIdentity"/>.
@@ -278,17 +295,19 @@ public virtual ClaimsIdentity CreateClaimsIdentity(SecurityToken securityToken,
278295
public IList<SecurityKey> IssuerSigningKeys { get; }
279296

280297
/// <summary>
281-
/// Gets or sets a delegate that will be used to validate the issuer of the token.
298+
/// Allows overriding the delegate that will be used to validate the issuer of the token.
282299
/// </summary>
300+
/// <exception cref="ArgumentNullException">Thrown when the value is set as null.</exception>
301+
/// <returns>The <see cref="IssuerValidationDelegateAsync"/> used to validate the issuer of a token</returns>
283302
public IssuerValidationDelegateAsync IssuerValidatorAsync
284303
{
285304
get
286305
{
287-
return _issuerValidationDelegate;
306+
return _issuerValidatorAsync;
288307
}
289308
set
290309
{
291-
_issuerValidationDelegate = value ?? throw LogHelper.LogArgumentNullException(nameof(value));
310+
_issuerValidatorAsync = value ?? throw new ArgumentNullException(nameof(value), "IssuerValidatorAsync cannot be set as null.");
292311
}
293312
}
294313

@@ -298,9 +317,21 @@ public IssuerValidationDelegateAsync IssuerValidatorAsync
298317
public TransformBeforeSignatureValidation TransformBeforeSignatureValidation { get; set; }
299318

300319
/// <summary>
301-
/// Gets or sets a delegate that will be used to validate the lifetime of the token
320+
/// Allows overriding the delegate that will be used to validate the lifetime of the token
302321
/// </summary>
303-
public LifetimeValidator LifetimeValidator { get; set; }
322+
/// <exception cref="ArgumentNullException">Thrown when the value is set as null.</exception>
323+
/// <returns>The <see cref="LifetimeValidatorDelegate"/> used to validate the lifetime of a token</returns>
324+
public LifetimeValidatorDelegate LifetimeValidator
325+
{
326+
get
327+
{
328+
return _lifetimeValidator;
329+
}
330+
set
331+
{
332+
_lifetimeValidator = value ?? throw new ArgumentNullException(nameof(value), "LifetimeValidator cannot be set as null.");
333+
}
334+
}
304335

305336
/// <summary>
306337
/// Gets or sets a <see cref="bool"/> that will decide if the token identifier claim needs to be logged.
@@ -430,26 +461,51 @@ public string RoleClaimType
430461
public ITokenReplayCache TokenReplayCache { get; set; }
431462

432463
/// <summary>
433-
/// Gets or sets a delegate that will be used to validate the token replay of the token
464+
/// Allows overriding the delegate that will be used to validate the token replay of the token.
434465
/// </summary>
435466
/// <remarks>
436-
/// If set, this delegate will be called to validate the token replay of the token, instead of default processing.
467+
/// If no delegate is set, the default implementation will be used.
437468
/// This means no default token replay validation will occur.
438-
/// Even if <see cref="ValidateTokenReplay"/> is false, this delegate will still be called.
439469
/// </remarks>
440-
public TokenReplayValidator TokenReplayValidator { get; set; }
470+
/// <exception cref="ArgumentNullException">Thrown when the value is set as null.</exception>
471+
/// <returns>The <see cref="TokenReplayValidatorDelegate"/> used to validate the token replay of the token.</returns>
472+
public TokenReplayValidatorDelegate TokenReplayValidator
473+
{
474+
get
475+
{
476+
return _tokenReplayValidator;
477+
}
478+
479+
set
480+
{
481+
_tokenReplayValidator = value ?? throw new ArgumentNullException(nameof(value), "TokenReplayValidator cannot be set as null.");
482+
}
483+
}
441484

442485
/// <summary>
443-
/// Gets or sets a delegate that will be used to validate the type of the token.
444-
/// If the token type cannot be validated, an exception MUST be thrown by the delegate.
486+
/// Allows overriding the delegate that will be used to validate the type of the token.
487+
/// If the token type cannot be validated, a <see cref="TokenTypeValidationResult"/> MUST be returned by the delegate.
445488
/// Note: the 'type' parameter may be null if it couldn't be extracted from its usual location.
446489
/// Implementations that need to resolve it from a different location can use the 'token' parameter.
447490
/// </summary>
448491
/// <remarks>
449-
/// If set, this delegate will be called to validate the 'type' of the token, instead of default processing.
450-
/// This means that no default 'type' validation will occur.
492+
/// If no delegate is set, the default implementation will be used. The default checks the type
493+
/// against the <see cref="ValidTypes"/> property, if the type is present then, it will succeed.
451494
/// </remarks>
452-
public TypeValidator TypeValidator { get; set; }
495+
/// <exception cref="ArgumentNullException">Thrown when the value is set as null.</exception>
496+
/// <returns>The <see cref="TypeValidatorDelegate"/> used to validate the token type of a token</returns>
497+
public TypeValidatorDelegate TypeValidator
498+
{
499+
get
500+
{
501+
return _typeValidator;
502+
}
503+
504+
set
505+
{
506+
_typeValidator = value ?? throw new ArgumentNullException(nameof(value), "TypeValidator cannot be set as null.");
507+
}
508+
}
453509

454510
/// <summary>
455511
/// Gets or sets a boolean to control if the LKG configuration will be used for token validation.
@@ -494,9 +550,21 @@ public string RoleClaimType
494550
/// Gets the <see cref="IList{String}"/> that contains valid types that will be used to check against the JWT header's 'typ' claim.
495551
/// If this property is not set, the 'typ' header claim will not be validated and all types will be accepted.
496552
/// In the case of a JWE, this property will ONLY apply to the inner token header.
497-
/// The default is <c>null</c>.
553+
/// The default is an empty collection.
498554
/// </summary>
499-
public IList<string> ValidTypes { get; }
555+
/// <exception cref="ArgumentNullException">Thrown when the value is set as null.</exception>
556+
/// <returns>The <see cref="IList{String}"/> that contains valid token types that will be used to check against the token's 'typ' claim.</returns>
557+
public IList<string> ValidTypes
558+
{
559+
get
560+
{
561+
return _validTokenTypes;
562+
}
563+
set
564+
{
565+
_validTokenTypes = value ?? throw new ArgumentNullException(nameof(value));
566+
}
567+
}
500568

501569
public bool ValidateActor { get; set; }
502570
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public static partial class Validators
1717
/// <param name="algorithm">The algorithm to be validated.</param>
1818
/// <param name="securityKey">The <see cref="SecurityKey"/> that signed the <see cref="SecurityToken"/>.</param>
1919
/// <param name="securityToken">The <see cref="SecurityToken"/> being validated.</param>
20-
/// <param name="validationParameters"><see cref="TokenValidationParameters"/> required for validation.</param>
20+
/// <param name="validationParameters"><see cref="ValidationParameters"/> required for validation.</param>
2121
/// <param name="callContext"></param>
2222
#pragma warning disable CA1801 // TODO: remove pragma disable once callContext is used for logging
2323
internal static AlgorithmValidationResult ValidateAlgorithm(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ namespace Microsoft.IdentityModel.Tokens
2020
/// <param name="callContext"></param>
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>
23-
internal delegate AudienceValidationResult ValidateAudience(
23+
internal delegate AudienceValidationResult AudienceValidatorDelegate(
2424
IEnumerable<string> audiences,
2525
SecurityToken? securityToken,
2626
TokenValidationParameters validationParameters,

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

Lines changed: 7 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ namespace Microsoft.IdentityModel.Tokens
1919
/// <param name="callContext"></param>
2020
/// <returns>A <see cref="IssuerValidationResult"/>that contains the results of validating the issuer.</returns>
2121
/// <remarks>This delegate is not expected to throw.</remarks>
22-
internal delegate LifetimeValidationResult LifetimeValidationDelegate(
22+
internal delegate LifetimeValidationResult LifetimeValidatorDelegate(
2323
DateTime? notBefore,
2424
DateTime? expires,
2525
SecurityToken? securityToken,
26-
TokenValidationParameters validationParameters,
26+
ValidationParameters validationParameters,
2727
CallContext callContext);
2828

2929
/// <summary>
@@ -37,18 +37,18 @@ public static partial class Validators
3737
/// <param name="notBefore">The 'notBefore' time found in the <see cref="SecurityToken"/>.</param>
3838
/// <param name="expires">The 'expiration' time found in the <see cref="SecurityToken"/>.</param>
3939
/// <param name="securityToken">The <see cref="SecurityToken"/> being validated.</param>
40-
/// <param name="validationParameters">The <see cref="TokenValidationParameters"/> to be used for validating the token.</param>
40+
/// <param name="validationParameters">The <see cref="ValidationParameters"/> to be used for validating the token.</param>
4141
/// <param name="callContext"></param>
4242
/// <returns>A <see cref="LifetimeValidationResult"/> indicating whether validation was successful, and providing a <see cref="SecurityTokenInvalidLifetimeException"/> if it was not.</returns>
4343
/// <exception cref="ArgumentNullException">If 'validationParameters' is null.</exception>
44-
/// <exception cref="SecurityTokenNoExpirationException">If 'expires.HasValue' is false and <see cref="TokenValidationParameters.RequireExpirationTime"/> is true.</exception>
44+
/// <exception cref="SecurityTokenNoExpirationException">If 'expires.HasValue' is false.</exception>
4545
/// <exception cref="SecurityTokenInvalidLifetimeException">If 'notBefore' is &gt; 'expires'.</exception>
4646
/// <exception cref="SecurityTokenNotYetValidException">If 'notBefore' is &gt; DateTime.UtcNow.</exception>
4747
/// <exception cref="SecurityTokenExpiredException">If 'expires' is &lt; DateTime.UtcNow.</exception>
48-
/// <remarks>All time comparisons apply <see cref="TokenValidationParameters.ClockSkew"/>.</remarks>
48+
/// <remarks>All time comparisons apply <see cref="ValidationParameters.ClockSkew"/>.</remarks>
4949
/// <remarks>Exceptions are not thrown, but embedded in <see cref="LifetimeValidationResult.Exception"/>.</remarks>
5050
#pragma warning disable CA1801 // TODO: remove pragma disable once callContext is used for logging
51-
internal static LifetimeValidationResult ValidateLifetime(DateTime? notBefore, DateTime? expires, SecurityToken? securityToken, TokenValidationParameters validationParameters, CallContext callContext)
51+
internal static LifetimeValidationResult ValidateLifetime(DateTime? notBefore, DateTime? expires, SecurityToken? securityToken, ValidationParameters validationParameters, CallContext callContext)
5252
#pragma warning restore CA1801
5353
{
5454
if (validationParameters == null)
@@ -63,16 +63,7 @@ internal static LifetimeValidationResult ValidateLifetime(DateTime? notBefore, D
6363
typeof(ArgumentNullException),
6464
new StackFrame(true)));
6565

66-
if (validationParameters.LifetimeValidator != null)
67-
return ValidateLifetimeUsingDelegate(notBefore, expires, securityToken, validationParameters);
68-
69-
if (!validationParameters.ValidateLifetime)
70-
{
71-
LogHelper.LogInformation(LogMessages.IDX10238);
72-
return new LifetimeValidationResult(notBefore, expires);
73-
}
74-
75-
if (!expires.HasValue && validationParameters.RequireExpirationTime)
66+
if (!expires.HasValue)
7667
return new LifetimeValidationResult(
7768
notBefore,
7869
expires,
@@ -130,42 +121,6 @@ internal static LifetimeValidationResult ValidateLifetime(DateTime? notBefore, D
130121

131122
return new LifetimeValidationResult(notBefore, expires);
132123
}
133-
134-
private static LifetimeValidationResult ValidateLifetimeUsingDelegate(DateTime? notBefore, DateTime? expires, SecurityToken? securityToken, TokenValidationParameters validationParameters)
135-
{
136-
try
137-
{
138-
if (!validationParameters.LifetimeValidator(notBefore, expires, securityToken, validationParameters))
139-
return new LifetimeValidationResult(
140-
notBefore,
141-
expires,
142-
ValidationFailureType.LifetimeValidationFailed,
143-
new ExceptionDetail(
144-
new MessageDetail(
145-
LogMessages.IDX10230,
146-
securityToken),
147-
typeof(SecurityTokenInvalidLifetimeException),
148-
new StackFrame(true)));
149-
150-
return new LifetimeValidationResult(notBefore, expires);
151-
}
152-
#pragma warning disable CA1031 // Do not catch general exception types
153-
catch (Exception delegateException)
154-
#pragma warning restore CA1031 // Do not catch general exception types
155-
{
156-
return new LifetimeValidationResult(
157-
notBefore,
158-
expires,
159-
ValidationFailureType.LifetimeValidationFailed,
160-
new ExceptionDetail(
161-
new MessageDetail(
162-
LogMessages.IDX10230,
163-
securityToken),
164-
delegateException.GetType(),
165-
new StackFrame(true),
166-
delegateException));
167-
}
168-
}
169124
}
170125
}
171126
#nullable restore

0 commit comments

Comments
 (0)