Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-factor Issuer Validator to Follow New Validation Model #2759

Merged
merged 4 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Microsoft.IdentityModel.Tokens/LogMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ internal static class LogMessages
public const string IDX10208 = "IDX10208: Unable to validate audience. validationParameters.ValidAudience is null or whitespace and validationParameters.ValidAudiences is null.";
public const string IDX10209 = "IDX10209: Token has length: '{0}' which is larger than the MaximumTokenSizeInBytes: '{1}'.";
public const string IDX10211 = "IDX10211: Unable to validate issuer. The 'issuer' parameter is null or whitespace.";
public const string IDX10212 = "IDX10212: Issuer validation failed. Issuer: '{0}'. Did not match any: validationParameters.ValidIssuers: '{1}' or validationParameters.ConfigurationManager.CurrentConfiguration.Issuer: '{2}'. For more details, see https://aka.ms/IdentityModel/issuer-validation. ";
public const string IDX10214 = "IDX10214: Audience validation failed. Audiences: '{0}'. Did not match: validationParameters.ValidAudience: '{1}' or validationParameters.ValidAudiences: '{2}'.";
public const string IDX10222 = "IDX10222: Lifetime validation failed. The token is not yet valid. ValidFrom (UTC): '{0}', Current time (UTC): '{1}'.";
public const string IDX10223 = "IDX10223: Lifetime validation failed. The token is expired. ValidTo (UTC): '{0}', Current time (UTC): '{1}'.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Security.Claims;
using System.Threading;
using Microsoft.IdentityModel.Abstractions;
using Microsoft.IdentityModel.Logging;

Expand All @@ -21,6 +20,7 @@ internal class ValidationParameters
private string _nameClaimType = ClaimsIdentity.DefaultNameClaimType;
private string _roleClaimType = ClaimsIdentity.DefaultRoleClaimType;
private Dictionary<string, object> _instancePropertyBag;
private IList<string> _validIssuers = [];
FuPingFranco marked this conversation as resolved.
Show resolved Hide resolved
private IList<string> _validTokenTypes = [];

private AlgorithmValidatorDelegate _algorithmValidator = Validators.ValidateAlgorithm;
Expand Down Expand Up @@ -518,10 +518,16 @@ public TypeValidatorDelegate TypeValidator
public IList<string> ValidAudiences { get; }

/// <summary>
/// Gets the <see cref="IList{String}"/> that contains valid issuers that will be used to check against the token's issuer.
/// The default is <c>null</c>.
/// Gets or sets the <see cref="IList{String}"/> that contains valid issuers that will be used to check against the token's issuer.
/// The default is an empty collection.
/// </summary>
public IList<string> ValidIssuers { get; }
/// <exception cref="ArgumentNullException">Thrown when the value is set as null.</exception>
/// <returns>The <see cref="IList{String}"/> that contains valid issuers that will be used to check against the token's 'iss' claim.</returns>
public IList<string> ValidIssuers
{
get { return _validIssuers; }
FuPingFranco marked this conversation as resolved.
Show resolved Hide resolved
set { _validIssuers = value ?? throw new ArgumentNullException(nameof(value), "ValidIssuers cannot be set as null."); }
}

/// <summary>
/// Gets the <see cref="IList{String}"/> that contains valid types that will be used to check against the JWT header's 'typ' claim.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ namespace Microsoft.IdentityModel.Tokens
/// </summary>
/// <param name="issuer">The issuer to validate.</param>
/// <param name="securityToken">The <see cref="SecurityToken"/> that is being validated.</param>
/// <param name="validationParameters">The <see cref="TokenValidationParameters"/> to be used for validating the token.</param>
/// <param name="validationParameters">The <see cref="ValidationParameters"/> to be used for validating the token.</param>
/// <param name="callContext"></param>
/// <param name="cancellationToken"></param>
/// <returns>A <see cref="IssuerValidationResult"/>that contains the results of validating the issuer.</returns>
/// <returns>An <see cref="IssuerValidationResult"/>that contains the results of validating the issuer.</returns>
/// <remarks>This delegate is not expected to throw.</remarks>
internal delegate Task<IssuerValidationResult> IssuerValidationDelegateAsync(
string issuer,
SecurityToken securityToken,
TokenValidationParameters validationParameters,
ValidationParameters validationParameters,
CallContext callContext,
CancellationToken cancellationToken);

Expand All @@ -37,19 +37,15 @@ public static partial class Validators
/// </summary>
/// <param name="issuer">The issuer to validate</param>
/// <param name="securityToken">The <see cref="SecurityToken"/> that is being validated.</param>
/// <param name="validationParameters">The <see cref="TokenValidationParameters"/> to be used for validating the token.</param>
/// <param name="validationParameters">The <see cref="ValidationParameters"/> to be used for validating the token.</param>
/// <param name="callContext"></param>
/// <param name="cancellationToken"></param>
/// <returns>The issuer to use when creating the "Claim"(s) in a "ClaimsIdentity".</returns>
/// <exception cref="ArgumentNullException">If 'validationParameters' is null.</exception>
/// <exception cref="ArgumentNullException">If 'issuer' is null or whitespace and <see cref="TokenValidationParameters.ValidateIssuer"/> is true.</exception>
/// <exception cref="SecurityTokenInvalidIssuerException">If <see cref="TokenValidationParameters.ValidIssuer"/> is null or whitespace and <see cref="TokenValidationParameters.ValidIssuers"/> is null.</exception>
/// <exception cref="SecurityTokenInvalidIssuerException">If 'issuer' failed to matched either <see cref="TokenValidationParameters.ValidIssuer"/> or one of <see cref="TokenValidationParameters.ValidIssuers"/>.</exception>
/// <returns>An <see cref="IssuerValidationResult"/>that contains the results of validating the issuer.</returns>
/// <remarks>An EXACT match is required.</remarks>
internal static async Task<IssuerValidationResult> ValidateIssuerAsync(
string issuer,
SecurityToken securityToken,
TokenValidationParameters validationParameters,
ValidationParameters validationParameters,
CallContext callContext,
CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -95,10 +91,8 @@ internal static async Task<IssuerValidationResult> ValidateIssuerAsync(
if (validationParameters.ConfigurationManager != null)
configuration = await validationParameters.ConfigurationManager.GetBaseConfigurationAsync(cancellationToken).ConfigureAwait(false);

// Throw if all possible places to validate against are null or empty
if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer)
&& validationParameters.ValidIssuers.IsNullOrEmpty()
&& string.IsNullOrWhiteSpace(configuration?.Issuer))
// Return failed IssuerValidationResult if all possible places to validate against are null or empty.
if (validationParameters.ValidIssuers.IsNullOrEmpty() && string.IsNullOrWhiteSpace(configuration?.Issuer))
FuPingFranco marked this conversation as resolved.
Show resolved Hide resolved
{
return new IssuerValidationResult(
issuer,
Expand Down Expand Up @@ -126,25 +120,19 @@ internal static async Task<IssuerValidationResult> ValidateIssuerAsync(
}
}

if (string.Equals(validationParameters.ValidIssuer, issuer))
if (validationParameters.ValidIssuers.Count != 0)
{
return new IssuerValidationResult(issuer,
IssuerValidationResult.ValidationSource.IssuerIsValidIssuer);
}

if (validationParameters.ValidIssuers != null)
{
foreach (string str in validationParameters.ValidIssuers)
for (int i = 0; i < validationParameters.ValidIssuers.Count; i++)
{
if (string.IsNullOrEmpty(str))
if (string.IsNullOrEmpty(validationParameters.ValidIssuers[i]))
{
if (LogHelper.IsEnabled(EventLogLevel.Informational))
LogHelper.LogInformation(LogMessages.IDX10262);

continue;
}

if (string.Equals(str, issuer))
if (string.Equals(validationParameters.ValidIssuers[i], issuer))
{
if (LogHelper.IsEnabled(EventLogLevel.Informational))
LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer));
Expand All @@ -160,9 +148,8 @@ internal static async Task<IssuerValidationResult> ValidateIssuerAsync(
ValidationFailureType.IssuerValidationFailed,
new ExceptionDetail(
new MessageDetail(
LogMessages.IDX10205,
LogMessages.IDX10212,
LogHelper.MarkAsNonPII(issuer),
LogHelper.MarkAsNonPII(validationParameters.ValidIssuer ?? "null"),
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidIssuers)),
LogHelper.MarkAsNonPII(configuration?.Issuer)),
typeof(SecurityTokenInvalidIssuerException),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static TheoryData<IssuerValidatorTheoryData> AsyncIssuerValidatorTestCase
theoryData.Add(new IssuerValidatorTheoryData
{
Issuer = null,
ValidationParameters = new TokenValidationParameters(),
ValidationParameters = new ValidationParameters(),
});

return theoryData;
Expand All @@ -52,7 +52,7 @@ public static TheoryData<IssuerValidatorTheoryData> AsyncIssuerValidatorTestCase
public class IssuerValidatorTheoryData : TheoryDataBase
{
public string Issuer { get; set; }
public TokenValidationParameters ValidationParameters { get; set; }
internal ValidationParameters ValidationParameters { get; set; }
public SecurityToken SecurityToken { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,26 +56,8 @@ public static TheoryData<IssuerValidationResultsTheoryData> IssuerValdationResul

string validIssuer = Guid.NewGuid().ToString();
string issClaim = Guid.NewGuid().ToString();
theoryData.Add(new IssuerValidationResultsTheoryData("Invalid_Issuer")
{
ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX10205:"),
Issuer = issClaim,
IssuerValidationResult = new IssuerValidationResult(
issClaim,
ValidationFailureType.IssuerValidationFailed,
new ExceptionDetail(
new MessageDetail(
LogMessages.IDX10205,
LogHelper.MarkAsNonPII(issClaim),
LogHelper.MarkAsNonPII(validIssuer),
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(null)),
LogHelper.MarkAsNonPII(null)),
typeof(SecurityTokenInvalidIssuerException),
new StackFrame(true))),
IsValid = false,
SecurityToken = JsonUtilities.CreateUnsignedJsonWebToken(JwtRegisteredClaimNames.Iss, issClaim),
ValidationParameters = new TokenValidationParameters { ValidIssuer = validIssuer }
});
var validConfig = new OpenIdConnectConfiguration() { Issuer = issClaim };
string[] validIssuers = new string[] { validIssuer };

theoryData.Add(new IssuerValidationResultsTheoryData("NULL_Issuer")
{
Expand All @@ -94,7 +76,7 @@ public static TheoryData<IssuerValidationResultsTheoryData> IssuerValdationResul
new StackFrame(true))),
IsValid = false,
SecurityToken = JsonUtilities.CreateUnsignedJsonWebToken(JwtRegisteredClaimNames.Iss, issClaim),
ValidationParameters = new TokenValidationParameters(),
ValidationParameters = new ValidationParameters()
});

theoryData.Add(new IssuerValidationResultsTheoryData("NULL_ValidationParameters")
Expand Down Expand Up @@ -132,10 +114,9 @@ public static TheoryData<IssuerValidationResultsTheoryData> IssuerValdationResul
null)),
IsValid = false,
SecurityToken = null,
ValidationParameters = new TokenValidationParameters()
ValidationParameters = new ValidationParameters()
});

var validConfig = new OpenIdConnectConfiguration() { Issuer = issClaim };
theoryData.Add(new IssuerValidationResultsTheoryData("Valid_FromConfig")
{
ExpectedException = ExpectedException.NoExceptionExpected,
Expand All @@ -145,40 +126,45 @@ public static TheoryData<IssuerValidationResultsTheoryData> IssuerValdationResul
IssuerValidationResult.ValidationSource.IssuerIsConfigurationIssuer),
IsValid = true,
SecurityToken = JsonUtilities.CreateUnsignedJsonWebToken(JwtRegisteredClaimNames.Iss, issClaim),
ValidationParameters = new TokenValidationParameters()
ValidationParameters = new ValidationParameters()
{
ConfigurationManager = new MockConfigurationManager<OpenIdConnectConfiguration>(validConfig)
}
});

theoryData.Add(new IssuerValidationResultsTheoryData("Valid_FromValidationParametersValidIssuer")
theoryData.Add(new IssuerValidationResultsTheoryData("Valid_FromValidationParametersValidIssuers")
{
ExpectedException = ExpectedException.NoExceptionExpected,
Issuer = issClaim,
IssuerValidationResult = new IssuerValidationResult(
issClaim,
IssuerValidationResult.ValidationSource.IssuerIsValidIssuer),
IssuerValidationResult.ValidationSource.IssuerIsAmongValidIssuers),
IsValid = true,
SecurityToken = JsonUtilities.CreateUnsignedJsonWebToken(JwtRegisteredClaimNames.Iss, issClaim),
ValidationParameters = new TokenValidationParameters()
ValidationParameters = new ValidationParameters()
{
ValidIssuer = issClaim
ValidIssuers = [issClaim]
}
});

theoryData.Add(new IssuerValidationResultsTheoryData("Valid_FromValidationParametersValidIssuers")
theoryData.Add(new IssuerValidationResultsTheoryData("Invalid_Issuer")
{
ExpectedException = ExpectedException.NoExceptionExpected,
ExpectedException = ExpectedException.SecurityTokenInvalidIssuerException("IDX10212:"),
Issuer = issClaim,
IssuerValidationResult = new IssuerValidationResult(
issClaim,
IssuerValidationResult.ValidationSource.IssuerIsAmongValidIssuers),
IsValid = true,
ValidationFailureType.IssuerValidationFailed,
new ExceptionDetail(
new MessageDetail(
LogMessages.IDX10212,
LogHelper.MarkAsNonPII(issClaim),
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validIssuers)),
LogHelper.MarkAsNonPII(null)),
typeof(SecurityTokenInvalidIssuerException),
new StackFrame(true))),
IsValid = false,
SecurityToken = JsonUtilities.CreateUnsignedJsonWebToken(JwtRegisteredClaimNames.Iss, issClaim),
ValidationParameters = new TokenValidationParameters()
{
ValidIssuers = [issClaim]
}
ValidationParameters = new ValidationParameters { ValidIssuers = [validIssuer] }
});

return theoryData;
Expand All @@ -202,7 +188,7 @@ public IssuerValidationResultsTheoryData(string testId) : base(testId)

public SecurityToken SecurityToken { get; set; }

public TokenValidationParameters ValidationParameters { get; set; }
internal ValidationParameters ValidationParameters { get; set; }

internal ValidationFailureType ValidationFailureType { get; set; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,25 @@ public void SetValidators_NullValue_ThrowsArgumentNullException()
Assert.Throws<ArgumentNullException>(() => validationParameters.AudienceValidator = null);
}

[Fact]
public void ValidIssuers_GetSet_ValidIssuersList()
{
var validationParameters = new ValidationParameters();
var validIssuers = new List<string> { "issuer1", "issuer2" };

validationParameters.ValidIssuers = validIssuers;

Assert.Equal(validIssuers, validationParameters.ValidIssuers);
}

[Fact]
public void ValidIssuers_SetNull_ThrowsArgumentNullException()
{
var validationParameters = new ValidationParameters();

Assert.Throws<ArgumentNullException>(() => validationParameters.ValidIssuers = null);
}

[Fact]
public void ValidTypes_Get_ReturnsValidTokenTypes()
{
Expand Down
Loading