Skip to content

Commit

Permalink
Regression tests: Token Type (#2932)
Browse files Browse the repository at this point in the history
* Renamed ValidationParameters.TypeValidator to ValidationParameters.TokenTypeValidator

* Removed log from TokenType validation

* Added TokenTypeValidationError to create the exception and assign the invalid token type. Updated the token type validator to use it.

* Adjusted the creation of SecurityTokenInvalidTypeException to return the correct invalid type when it is empty or null

* Added regression/comparison tests around token type validation scenarios

* Added test for valid scenario where there are no valid types specified

* Return null for the invalid token type if the type is an empty string to keep considency with existing tests.

* Updated renamed variable

* Addressed post merge issues
  • Loading branch information
iNinja authored Oct 24, 2024
1 parent 4a28a69 commit aed25ca
Show file tree
Hide file tree
Showing 13 changed files with 206 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ await ValidateJWSAsync(actorToken, actorParameters, configuration, callContext,
actorValidationResult = innerActorValidationResult;
}

ValidationResult<ValidatedTokenType> typeValidationResult = validationParameters.TypeValidator(
ValidationResult<ValidatedTokenType> typeValidationResult = validationParameters.TokenTypeValidator(
jsonWebToken.Typ, jsonWebToken, validationParameters, callContext);
if (!typeValidationResult.IsValid)
{
Expand Down
2 changes: 0 additions & 2 deletions src/Microsoft.IdentityModel.Tokens/InternalAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -550,8 +550,6 @@ Microsoft.IdentityModel.Tokens.ValidationParameters.TokenReplayValidator.get ->
Microsoft.IdentityModel.Tokens.ValidationParameters.TokenReplayValidator.set -> void
Microsoft.IdentityModel.Tokens.ValidationParameters.TryAllIssuerSigningKeys.get -> bool
Microsoft.IdentityModel.Tokens.ValidationParameters.TryAllIssuerSigningKeys.set -> void
Microsoft.IdentityModel.Tokens.ValidationParameters.TypeValidator.get -> Microsoft.IdentityModel.Tokens.TokenTypeValidationDelegate
Microsoft.IdentityModel.Tokens.ValidationParameters.TypeValidator.set -> void
Microsoft.IdentityModel.Tokens.ValidationParameters.ValidAlgorithms.get -> System.Collections.Generic.IList<string>
Microsoft.IdentityModel.Tokens.ValidationParameters.ValidAlgorithms.set -> void
Microsoft.IdentityModel.Tokens.ValidationParameters.ValidateActor.get -> bool
Expand Down
8 changes: 7 additions & 1 deletion src/Microsoft.IdentityModel.Tokens/InternalAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedConfiguration
Microsoft.IdentityModel.Tokens.IssuerValidationSource.IssuerMatchedValidationParameters = 2 -> Microsoft.IdentityModel.Tokens.IssuerValidationSource
Microsoft.IdentityModel.Tokens.LifetimeValidationError._expires -> System.DateTime
Microsoft.IdentityModel.Tokens.LifetimeValidationError._notBefore -> System.DateTime
Microsoft.IdentityModel.Tokens.TokenTypeValidationError
Microsoft.IdentityModel.Tokens.TokenTypeValidationError.TokenTypeValidationError(Microsoft.IdentityModel.Tokens.MessageDetail messageDetail, System.Type exceptionType, System.Diagnostics.StackFrame stackFrame, string invalidTokenType) -> void
Microsoft.IdentityModel.Tokens.TokenTypeValidationError._invalidTokenType -> string
Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.get -> System.TimeProvider
Microsoft.IdentityModel.Tokens.TokenValidationParameters.TimeProvider.set -> void
Microsoft.IdentityModel.Tokens.ValidationError.GetException(System.Type exceptionType, System.Exception innerException) -> System.Exception
Microsoft.IdentityModel.Tokens.ValidationParameters.TokenTypeValidator.get -> Microsoft.IdentityModel.Tokens.TokenTypeValidationDelegate
Microsoft.IdentityModel.Tokens.ValidationParameters.TokenTypeValidator.set -> void
Microsoft.IdentityModel.Tokens.ValidationResult<TResult>.Error.get -> Microsoft.IdentityModel.Tokens.ValidationError
Microsoft.IdentityModel.Tokens.ValidationResult<TResult>.IsValid.get -> bool
Microsoft.IdentityModel.Tokens.ValidationResult<TResult>.Result.get -> TResult
override Microsoft.IdentityModel.Tokens.AlgorithmValidationError.GetException() -> System.Exception
override Microsoft.IdentityModel.Tokens.TokenTypeValidationError.GetException() -> System.Exception
static Microsoft.IdentityModel.Tokens.AudienceValidationError.AudiencesCountZero -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.AudienceValidationError.AudiencesNull -> System.Diagnostics.StackFrame
static Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidateAudienceFailed -> System.Diagnostics.StackFrame
Expand All @@ -25,4 +31,4 @@ static Microsoft.IdentityModel.Tokens.AudienceValidationError.ValidationParamete
static Microsoft.IdentityModel.Tokens.Utility.SerializeAsSingleCommaDelimitedString(System.Collections.Generic.IList<string> strings) -> string
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.NoTokenAudiencesProvided -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.NoValidationParameterAudiencesProvided -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.SignatureAlgorithmValidationFailed -> Microsoft.IdentityModel.Tokens.ValidationFailureType
static readonly Microsoft.IdentityModel.Tokens.ValidationFailureType.SignatureAlgorithmValidationFailed -> Microsoft.IdentityModel.Tokens.ValidationFailureType
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 TokenTypeValidationError : ValidationError
{
protected string? _invalidTokenType;

internal TokenTypeValidationError(
MessageDetail messageDetail,
Type exceptionType,
StackFrame stackFrame,
string? invalidTokenType)
: base(messageDetail, ValidationFailureType.TokenTypeValidationFailed, exceptionType, stackFrame)
{
_invalidTokenType = invalidTokenType;
}

internal override Exception GetException()
{
if (ExceptionType == typeof(SecurityTokenInvalidTypeException))
{
SecurityTokenInvalidTypeException exception = new(MessageDetail.Message, InnerException)
{
InvalidType = _invalidTokenType
};

return exception;
}

return base.GetException();
}
}
}
#nullable restore
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ protected ValidationParameters(ValidationParameters other)
TokenDecryptionKeys = other.TokenDecryptionKeys;
TokenReplayCache = other.TokenReplayCache;
TokenReplayValidator = other.TokenReplayValidator;
TypeValidator = other.TypeValidator;
TokenTypeValidator = other.TokenTypeValidator;
ValidateActor = other.ValidateActor;
ValidateSignatureLast = other.ValidateSignatureLast;
ValidateWithLKG = other.ValidateWithLKG;
Expand Down Expand Up @@ -499,7 +499,7 @@ public TokenReplayValidationDelegate TokenReplayValidator
/// </remarks>
/// <exception cref="ArgumentNullException">Thrown when the value is set as null.</exception>
/// <returns>The <see cref="TokenTypeValidationDelegate"/> used to validate the token type of a token</returns>
public TokenTypeValidationDelegate TypeValidator
public TokenTypeValidationDelegate TokenTypeValidator
{
get { return _tokenTypeValidator; }
set { _tokenTypeValidator = value ?? throw new ArgumentNullException(nameof(value), "TypeValidator cannot be set as null."); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,27 +56,28 @@ internal static ValidationResult<ValidatedTokenType> ValidateTokenType(

if (validationParameters.ValidTypes.Count == 0)
{
LogHelper.LogVerbose(LogMessages.IDX10255);
//TODO: Move to CallContext?
//LogHelper.LogVerbose(LogMessages.IDX10255);
return new ValidatedTokenType(type ?? "null", validationParameters.ValidTypes.Count);
}

if (string.IsNullOrEmpty(type))
return new ValidationError(
return new TokenTypeValidationError(
new MessageDetail(LogMessages.IDX10256),
ValidationFailureType.TokenTypeValidationFailed,
typeof(SecurityTokenInvalidTypeException),
new StackFrame(true));
new StackFrame(true),
null); // even if it is empty, we report null to match the original behaviour.

if (!validationParameters.ValidTypes.Contains(type, StringComparer.Ordinal))
{
return new ValidationError(
return new TokenTypeValidationError(
new MessageDetail(
LogMessages.IDX10257,
LogHelper.MarkAsNonPII(type),
LogHelper.MarkAsNonPII(Utility.SerializeAsSingleCommaDelimitedString(validationParameters.ValidTypes))),
ValidationFailureType.TokenTypeValidationFailed,
typeof(SecurityTokenInvalidTypeException),
new StackFrame(true));
new StackFrame(true),
type);
}

// TODO: Move to CallContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ static ValidationParameters CreateValidationParameters(
validationParameters.IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation;
validationParameters.LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation;
validationParameters.TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation;
validationParameters.TypeValidator = SkipValidationDelegates.SkipTokenTypeValidation;
validationParameters.TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation;

return validationParameters;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ static ValidationParameters CreateValidationParameters(
validationParameters.IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation;
validationParameters.LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation;
validationParameters.SignatureValidator = SkipValidationDelegates.SkipSignatureValidation;
validationParameters.TypeValidator = SkipValidationDelegates.SkipTokenTypeValidation;
validationParameters.TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation;

return validationParameters;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ static ValidationParameters CreateValidationParameters(
validationParameters.IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation;
validationParameters.LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation;
validationParameters.TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation;
validationParameters.TypeValidator = SkipValidationDelegates.SkipTokenTypeValidation;
validationParameters.TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation;

return validationParameters;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ static ValidationParameters CreateValidationParameters(LifetimeValidationDelegat
validationParameters.IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation;
validationParameters.IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation;
validationParameters.SignatureValidator = SkipValidationDelegates.SkipSignatureValidation;
validationParameters.TypeValidator = SkipValidationDelegates.SkipTokenTypeValidation;
validationParameters.TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation;

return validationParameters;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License.

#nullable enable
using System;
using System.Threading.Tasks;
using Microsoft.IdentityModel.TestUtils;
using Microsoft.IdentityModel.Tokens;
Expand Down Expand Up @@ -122,7 +121,7 @@ static ValidationParameters CreateValidationParameters(
validationParameters.IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation;
validationParameters.LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation;
validationParameters.TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation;
validationParameters.TypeValidator = SkipValidationDelegates.SkipTokenTypeValidation;
validationParameters.TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation;
validationParameters.TryAllIssuerSigningKeys = tryAllKeys;

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

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

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

string jwtString = CreateTokenForTokenTypeValidation(theoryData.UseEmptyType, theoryData.CustomTokenType);

await ValidateAndCompareResults(jwtString, theoryData, context);

TestUtilities.AssertFailIfErrors(context);
}

public static TheoryData<ValidateTokenAsyncTokenTypeTheoryData> ValidateTokenAsync_TokenTypeTestCases
{
get
{
var theoryData = new TheoryData<ValidateTokenAsyncTokenTypeTheoryData>();

theoryData.Add(new ValidateTokenAsyncTokenTypeTheoryData("Valid_JwtToken")
{
TokenValidationParameters = CreateTokenValidationParameters(),
ValidationParameters = CreateValidationParameters(),
});

theoryData.Add(new ValidateTokenAsyncTokenTypeTheoryData("Valid_UnknownTokenType_NoValidTokenTypes")
{
// If there are no valid token types, any token type is valid
CustomTokenType = "SomeUnknownType",
TokenValidationParameters = CreateTokenValidationParameters(null),
ValidationParameters = CreateValidationParameters(null),
});

theoryData.Add(new ValidateTokenAsyncTokenTypeTheoryData("Valid_CustomToken_AddedAsValidTokenType")
{
CustomTokenType = "PPT",
TokenValidationParameters = CreateTokenValidationParameters(validTokenType: "PPT"),
ValidationParameters = CreateValidationParameters(validTokenType: "PPT"),
});

theoryData.Add(new ValidateTokenAsyncTokenTypeTheoryData("Invalid_CustomToken_NotAddedAsValidTokenType")
{
CustomTokenType = "PPT",
TokenValidationParameters = CreateTokenValidationParameters(),
ValidationParameters = CreateValidationParameters(),
ExpectedIsValid = false,
ExpectedException = ExpectedException.SecurityTokenInvalidTypeException("IDX10257:"),
});

theoryData.Add(new ValidateTokenAsyncTokenTypeTheoryData("Invalid_EmptyTokenType")
{
UseEmptyType = true,
TokenValidationParameters = CreateTokenValidationParameters(),
ValidationParameters = CreateValidationParameters(),
ExpectedIsValid = false,
ExpectedException = ExpectedException.SecurityTokenInvalidTypeException("IDX10256:"),
});

return theoryData;

static TokenValidationParameters CreateTokenValidationParameters(string? validTokenType = "JWT")
{
// only validate the signature and issuer signing key
var tokenValidationParameters = new TokenValidationParameters()
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateLifetime = false,
ValidateTokenReplay = false,
ValidateIssuerSigningKey = false,
RequireSignedTokens = false,
};

if (validTokenType is not null)
tokenValidationParameters.ValidTypes = [validTokenType];

return tokenValidationParameters;
}

static ValidationParameters CreateValidationParameters(string? validTokenType = "JWT")
{
ValidationParameters validationParameters = new ValidationParameters();

// Skip all validations except token type
validationParameters.AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation;
validationParameters.AudienceValidator = SkipValidationDelegates.SkipAudienceValidation;
validationParameters.IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation;
validationParameters.IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation;
validationParameters.LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation;
validationParameters.SignatureValidator = SkipValidationDelegates.SkipSignatureValidation;
validationParameters.TokenReplayValidator = SkipValidationDelegates.SkipTokenReplayValidation;

if (validTokenType is not null)
validationParameters.ValidTypes.Add(validTokenType);

return validationParameters;
}
}
}

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

public bool UseEmptyType { get; set; } = false;

public string? CustomTokenType { get; set; } = null;
}

// Custom JWT with empty string for type
private static string emptyTypeJWT = "eyJ0eXAiOiIiLCJhbGciOiJub25lIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTcyOTY5NDI1OCwiZXhwIjoxNzI5Njk3ODU4fQ.";

private static string CreateTokenForTokenTypeValidation(bool useEmptyType = false, string? tokenType = null)
{
if (useEmptyType)
return emptyTypeJWT;

JsonWebTokenHandler jsonWebTokenHandler = new JsonWebTokenHandler();

SecurityTokenDescriptor securityTokenDescriptor = new SecurityTokenDescriptor
{
Subject = Default.ClaimsIdentity,
TokenType = tokenType ?? "JWT",
};

return jsonWebTokenHandler.CreateToken(securityTokenDescriptor);
}
}
}
#nullable restore
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public void SetValidators_NullValue_ThrowsArgumentNullException()
Assert.Throws<ArgumentNullException>(() => validationParameters.IssuerValidatorAsync = null);
Assert.Throws<ArgumentNullException>(() => validationParameters.TokenReplayValidator = null);
Assert.Throws<ArgumentNullException>(() => validationParameters.LifetimeValidator = null);
Assert.Throws<ArgumentNullException>(() => validationParameters.TypeValidator = null);
Assert.Throws<ArgumentNullException>(() => validationParameters.TokenTypeValidator = null);
Assert.Throws<ArgumentNullException>(() => validationParameters.AudienceValidator = null);
Assert.Throws<ArgumentNullException>(() => validationParameters.IssuerSigningKeyValidator = null);
}
Expand Down

0 comments on commit aed25ca

Please sign in to comment.