Skip to content

Commit

Permalink
Regression tests: Issuer (#2868)
Browse files Browse the repository at this point in the history
* Renamed CreateToken methods in audience and lifetime regression tests

* Added custom ValidationError class for issuer errors. Updated IssuerValidationDelegateAsync to use it.

* Added JWT issuer regression tests

* Update test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ValidateTokenAsyncTests.Issuer.cs

Co-authored-by: kellyyangsong <[email protected]>

* Added IssuerValidationError to InternalAPI.Unshipped. Made constructor internal for the time being

* Updated exception creation in IssuerValidationError

* Adjusted unshipped API contents with the IDE suggestions

---------

Co-authored-by: kellyyangsong <[email protected]>
  • Loading branch information
iNinja and kellyyangsong authored Oct 15, 2024
1 parent 3dab668 commit 876def8
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 19 deletions.
3 changes: 2 additions & 1 deletion src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10001 = "IDX10001: Invalid argument '{0}'. Argument must be of type '{1}'." -> string
Microsoft.IdentityModel.Tokens.IssuerValidationError
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10001 = "IDX10001: Invalid argument '{0}'. Argument must be of type '{1}'." -> string
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10502 = "IDX10502: Signature validation failed. The token's kid is: '{0}', but did not match any keys in ValidationParameters or Configuration and TryAllIssuerSigningKeys is false. Number of keys in ValidationParameters: '{1}'. \nNumber of keys in Configuration: '{2}'.\ntoken: '{3}'." -> string
const Microsoft.IdentityModel.Tokens.LogMessages.IDX10518 = "IDX10518: Signature validation failed. Algorithm validation failed with error: '{0}'." -> string
Microsoft.IdentityModel.Tokens.AlgorithmValidationDelegate
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
Microsoft.IdentityModel.Tokens.AsymmetricAdapter.DecryptWithRsaCryptoServiceProviderProxy(byte[] bytes) -> byte[]
Microsoft.IdentityModel.Tokens.IssuerValidationError.IssuerValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidIssuer) -> void
override Microsoft.IdentityModel.Tokens.IssuerValidationError.GetException() -> System.Exception
Microsoft.IdentityModel.Tokens.AsymmetricAdapter.DecryptWithRsaCryptoServiceProviderProxy(byte[] bytes) -> byte[]
Microsoft.IdentityModel.Tokens.AsymmetricAdapter.EncryptWithRsaCryptoServiceProviderProxy(byte[] bytes) -> byte[]
Microsoft.IdentityModel.Tokens.AsymmetricAdapter.SignWithRsaCryptoServiceProviderProxy(byte[] bytes) -> byte[]
Microsoft.IdentityModel.Tokens.AsymmetricAdapter.SignWithRsaCryptoServiceProviderProxyUsingOffset(byte[] bytes, int offset, int length) -> byte[]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
static readonly Microsoft.IdentityModel.Tokens.Json.JsonWebKeySerializer.JsonWebKeyParameterNamesUpperCase -> System.Collections.Generic.HashSet<string>
Microsoft.IdentityModel.Tokens.IssuerValidationError.IssuerValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidIssuer) -> void
override Microsoft.IdentityModel.Tokens.IssuerValidationError.GetException() -> System.Exception
static readonly Microsoft.IdentityModel.Tokens.Json.JsonWebKeySerializer.JsonWebKeyParameterNamesUpperCase -> System.Collections.Generic.HashSet<string>
Microsoft.IdentityModel.Tokens.EcdhKeyExchangeProvider.GetEncryptionAlgorithm() -> string
Microsoft.IdentityModel.Tokens.SignUsingSpanDelegate
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
static readonly Microsoft.IdentityModel.Tokens.Json.JsonWebKeySerializer.JsonWebKeyParameterNamesUpperCase -> System.Collections.Frozen.FrozenSet<string>
Microsoft.IdentityModel.Tokens.IssuerValidationError.IssuerValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidIssuer) -> void
override Microsoft.IdentityModel.Tokens.IssuerValidationError.GetException() -> System.Exception
static readonly Microsoft.IdentityModel.Tokens.Json.JsonWebKeySerializer.JsonWebKeyParameterNamesUpperCase -> System.Collections.Frozen.FrozenSet<string>
Microsoft.IdentityModel.Tokens.EcdhKeyExchangeProvider.GetEncryptionAlgorithm() -> string
Microsoft.IdentityModel.Tokens.SignUsingSpanDelegate
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
static readonly Microsoft.IdentityModel.Tokens.Json.JsonWebKeySerializer.JsonWebKeyParameterNamesUpperCase -> System.Collections.Frozen.FrozenSet<string>
Microsoft.IdentityModel.Tokens.IssuerValidationError.IssuerValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidIssuer) -> void
override Microsoft.IdentityModel.Tokens.IssuerValidationError.GetException() -> System.Exception
static readonly Microsoft.IdentityModel.Tokens.Json.JsonWebKeySerializer.JsonWebKeyParameterNamesUpperCase -> System.Collections.Frozen.FrozenSet<string>
Microsoft.IdentityModel.Tokens.EcdhKeyExchangeProvider.GetEncryptionAlgorithm() -> string
Microsoft.IdentityModel.Tokens.SignUsingSpanDelegate
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
static readonly Microsoft.IdentityModel.Tokens.Json.JsonWebKeySerializer.JsonWebKeyParameterNamesUpperCase -> System.Collections.Generic.HashSet<string>
Microsoft.IdentityModel.Tokens.IssuerValidationError.IssuerValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidIssuer) -> void
override Microsoft.IdentityModel.Tokens.IssuerValidationError.GetException() -> System.Exception
static readonly Microsoft.IdentityModel.Tokens.Json.JsonWebKeySerializer.JsonWebKeyParameterNamesUpperCase -> System.Collections.Generic.HashSet<string>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Diagnostics;

#nullable enable
namespace Microsoft.IdentityModel.Tokens
{
internal class IssuerValidationError : ValidationError
{
private string? _invalidIssuer;

internal IssuerValidationError(
MessageDetail messageDetail,
Type exceptionType,
StackFrame stackFrame,
string? invalidIssuer)
: base(messageDetail, ValidationFailureType.IssuerValidationFailed, exceptionType, stackFrame)
{
_invalidIssuer = invalidIssuer;
}

public override Exception GetException()
{
if (ExceptionType == typeof(SecurityTokenInvalidIssuerException))
{
SecurityTokenInvalidIssuerException exception = new(MessageDetail.Message, InnerException)
{
InvalidIssuer = _invalidIssuer
};

return exception;
}

return base.GetException();
}
}
}
#nullable restore
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ internal static async Task<ValidationResult<ValidatedIssuer>> ValidateIssuerAsyn
{
if (string.IsNullOrWhiteSpace(issuer))
{
return new ValidationError(
return new IssuerValidationError(
new MessageDetail(LogMessages.IDX10211),
ValidationFailureType.IssuerValidationFailed,
typeof(SecurityTokenInvalidIssuerException),
new StackFrame(true));
new StackFrame(true),
issuer);
}

if (validationParameters == null)
Expand All @@ -84,11 +84,11 @@ internal static async Task<ValidationResult<ValidatedIssuer>> ValidateIssuerAsyn

// Return failed IssuerValidationResult if all possible places to validate against are null or empty.
if (validationParameters.ValidIssuers.Count == 0 && string.IsNullOrWhiteSpace(configuration?.Issuer))
return new ValidationError(
return new IssuerValidationError(
new MessageDetail(LogMessages.IDX10211),
ValidationFailureType.IssuerValidationFailed,
typeof(SecurityTokenInvalidIssuerException),
new StackFrame(true));
new StackFrame(true),
issuer);

if (configuration != null)
{
Expand Down Expand Up @@ -130,15 +130,15 @@ internal static async Task<ValidationResult<ValidatedIssuer>> ValidateIssuerAsyn
}
}

return new ValidationError(
return new IssuerValidationError(
new MessageDetail(
LogMessages.IDX10212,
LogHelper.MarkAsNonPII(issuer),
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidIssuers)),
LogHelper.MarkAsNonPII(configuration?.Issuer)),
ValidationFailureType.IssuerValidationFailed,
typeof(SecurityTokenInvalidIssuerException),
new StackFrame(true));
new StackFrame(true),
issuer);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public async Task ValidateTokenAsync_Audience(ValidateTokenAsyncAudienceTheoryDa
{
var context = TestUtilities.WriteHeader($"{this}.ValidateTokenAsync_Audience", theoryData);

string jwtString = CreateToken(theoryData.Audience);
string jwtString = CreateTokenWithAudience(theoryData.Audience);

await ValidateAndCompareResults(jwtString, theoryData, context);

Expand Down Expand Up @@ -155,7 +155,7 @@ public ValidateTokenAsyncAudienceTheoryData(string testId) : base(testId) { }
public string? Audience { get; internal set; } = Default.Audience;
}

private static string CreateToken(string? audience)
private static string CreateTokenWithAudience(string? audience)
{
JsonWebTokenHandler jsonWebTokenHandler = new JsonWebTokenHandler();

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

#nullable enable
using System.Threading.Tasks;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.TestUtils;
using Microsoft.IdentityModel.Tokens;
using Xunit;

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

string jwtString = CreateTokenWithIssuer(theoryData.TokenIssuer);

await ValidateAndCompareResults(jwtString, theoryData, context);

TestUtilities.AssertFailIfErrors(context);
}

public static TheoryData<ValidateTokenAsyncIssuerTheoryData> ValidateTokenAsync_IssuerTestCases
{
get
{
return new TheoryData<ValidateTokenAsyncIssuerTheoryData>
{
new ValidateTokenAsyncIssuerTheoryData("Valid_IssuerIsValidIssuer")
{
TokenIssuer = Default.Issuer,
TokenValidationParameters = CreateTokenValidationParameters(validIssuer: Default.Issuer),
ValidationParameters = CreateValidationParameters(validIssuer: Default.Issuer),
},
new ValidateTokenAsyncIssuerTheoryData("Valid_IssuerIsConfigurationIssuer")
{
TokenIssuer = Default.Issuer,
TokenValidationParameters = CreateTokenValidationParameters(configurationIssuer: Default.Issuer),
ValidationParameters = CreateValidationParameters(configurationIssuer: Default.Issuer),
},
new ValidateTokenAsyncIssuerTheoryData("Invalid_IssuerIsNotValid")
{
TokenIssuer = "InvalidIssuer",
TokenValidationParameters = CreateTokenValidationParameters(validIssuer: Default.Issuer),
ValidationParameters = CreateValidationParameters(validIssuer: Default.Issuer),
ExpectedIsValid = false,
ExpectedException = new ExpectedException(typeof(SecurityTokenInvalidIssuerException), "IDX10205:"),
ExpectedExceptionValidationParameters = new ExpectedException(typeof(SecurityTokenInvalidIssuerException), "IDX10212:"),
},
new ValidateTokenAsyncIssuerTheoryData("Invalid_IssuerIsNull")
{
TokenIssuer = null,
TokenValidationParameters = CreateTokenValidationParameters(validIssuer: Default.Issuer),
ValidationParameters = CreateValidationParameters(validIssuer: Default.Issuer),
ExpectedIsValid = false,
ExpectedException = new ExpectedException(typeof(SecurityTokenInvalidIssuerException), "IDX10211:"),
},
new ValidateTokenAsyncIssuerTheoryData("Invalid_IssuerIsEmpty")
{
TokenIssuer = string.Empty,
TokenValidationParameters = CreateTokenValidationParameters(validIssuer: Default.Issuer),
ValidationParameters = CreateValidationParameters(validIssuer: Default.Issuer),
ExpectedIsValid = false,
ExpectedException = new ExpectedException(typeof(SecurityTokenInvalidIssuerException), "IDX10211:"),
},
new ValidateTokenAsyncIssuerTheoryData("Invalid_NoValidIssuersProvided")
{
TokenIssuer = Default.Issuer,
TokenValidationParameters = CreateTokenValidationParameters(),
ValidationParameters = CreateValidationParameters(),
ExpectedIsValid = false,
ExpectedException = new ExpectedException(typeof(SecurityTokenInvalidIssuerException), "IDX10204:"),
ExpectedExceptionValidationParameters = new ExpectedException(typeof(SecurityTokenInvalidIssuerException), "IDX10211:"),
},
};

static TokenValidationParameters CreateTokenValidationParameters(
string? validIssuer = null, string? configurationIssuer = null)
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidateTokenReplay = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = Default.AsymmetricSigningKey,
ValidAudiences = [Default.Audience],
ValidIssuer = validIssuer
};

if (configurationIssuer is not null)
{
var validConfig = new OpenIdConnectConfiguration() { Issuer = configurationIssuer };
tokenValidationParameters.ConfigurationManager = new MockConfigurationManager<OpenIdConnectConfiguration>(validConfig);
}

return tokenValidationParameters;
}

static ValidationParameters CreateValidationParameters(
string? validIssuer = null, string? configurationIssuer = null)
{
ValidationParameters validationParameters = new ValidationParameters();
validationParameters.ValidAudiences.Add(Default.Audience);
validationParameters.IssuerSigningKeys.Add(Default.AsymmetricSigningKey);

if (configurationIssuer is not null)
{
var validConfig = new OpenIdConnectConfiguration() { Issuer = configurationIssuer };
validationParameters.ConfigurationManager = new MockConfigurationManager<OpenIdConnectConfiguration>(validConfig);
}

if (validIssuer is not null)
validationParameters.ValidIssuers.Add(validIssuer);

return validationParameters;
}
}
}

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

public string? TokenIssuer { get; set; }
}

private static string CreateTokenWithIssuer(string? issuer)
{
JsonWebTokenHandler jsonWebTokenHandler = new JsonWebTokenHandler();

SecurityTokenDescriptor securityTokenDescriptor = new SecurityTokenDescriptor
{
Subject = Default.ClaimsIdentity,
SigningCredentials = Default.AsymmetricSigningCredentials,
Audience = Default.Audience,
Issuer = issuer,
};

return jsonWebTokenHandler.CreateToken(securityTokenDescriptor);
}
}
}
#nullable restore
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public async Task ValidateTokenAsync_Lifetime(ValidateTokenAsyncLifetimeTheoryDa
{
var context = TestUtilities.WriteHeader($"{this}.ValidateTokenAsync_Lifetime", theoryData);

string jwtString = CreateToken(theoryData.IssuedAt, theoryData.NotBefore, theoryData.Expires);
string jwtString = CreateTokenWithLifetime(theoryData.IssuedAt, theoryData.NotBefore, theoryData.Expires);

await ValidateAndCompareResults(jwtString, theoryData, context);

Expand Down Expand Up @@ -155,7 +155,7 @@ public ValidateTokenAsyncLifetimeTheoryData(string testId) : base(testId) { }
public DateTime? Expires { get; set; }
}

private static string CreateToken(DateTime? issuedAt, DateTime? notBefore, DateTime? expires)
private static string CreateTokenWithLifetime(DateTime? issuedAt, DateTime? notBefore, DateTime? expires)
{
JsonWebTokenHandler jsonWebTokenHandler = new JsonWebTokenHandler();
jsonWebTokenHandler.SetDefaultTimesOnTokenCreation = false; // Allow for null values to be passed in to validate.
Expand Down

0 comments on commit 876def8

Please sign in to comment.