Skip to content

Commit

Permalink
Regression tests: Token Replay (#2931)
Browse files Browse the repository at this point in the history
* Removed calls to MarkAsUnsafeSecurityArtifact as they was printing the tokens in clear text and we cannot use JwtTokenUtilities.SafeLogJwt due to the fact that this handles other SecurityTokens as well.

* Removed duplicated CreateToken method

* Added regression/comparison tests for JWT on token replay scenarios

* Updated unit tests to reflect updated exception

* Resolve post merge issue
  • Loading branch information
iNinja authored Oct 24, 2024
1 parent aed25ca commit 788a7a4
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using System.Diagnostics;
using Microsoft.IdentityModel.Logging;

namespace Microsoft.IdentityModel.Tokens
{
Expand Down Expand Up @@ -60,16 +59,16 @@ public static partial class Validators
return new ValidationError(
new MessageDetail(
LogMessages.IDX10227,
LogHelper.MarkAsUnsafeSecurityArtifact(securityToken, t => t.ToString())),
securityToken),
ValidationFailureType.TokenReplayValidationFailed,
typeof(SecurityTokenReplayDetectedException),
typeof(SecurityTokenNoExpirationException),
new StackFrame(true));

if (validationParameters.TokenReplayCache.TryFind(securityToken))
return new ValidationError(
new MessageDetail(
LogMessages.IDX10228,
LogHelper.MarkAsUnsafeSecurityArtifact(securityToken, t => t.ToString())),
securityToken),
ValidationFailureType.TokenReplayValidationFailed,
typeof(SecurityTokenReplayDetectedException),
new StackFrame(true));
Expand All @@ -78,7 +77,7 @@ public static partial class Validators
return new ValidationError(
new MessageDetail(
LogMessages.IDX10229,
LogHelper.MarkAsUnsafeSecurityArtifact(securityToken, t => t.ToString())),
securityToken),
ValidationFailureType.TokenReplayValidationFailed,
typeof(SecurityTokenReplayAddFailedException),
new StackFrame(true));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public async Task ValidateTokenAsync_Lifetime_Extensibility(ValidateTokenAsyncLi
{
var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_Lifetime_Extensibility)}", theoryData);

string jwtString = CreateToken(theoryData.IssuedAt, theoryData.NotBefore, theoryData.Expires);
string jwtString = CreateTokenWithLifetime(theoryData.IssuedAt, theoryData.NotBefore, theoryData.Expires);
var handler = new JsonWebTokenHandler();

ValidationResult<ValidatedToken> validationResult;
Expand Down Expand Up @@ -300,22 +300,6 @@ internal override Exception GetException()
return base.GetException();
}
}

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

SecurityTokenDescriptor securityTokenDescriptor = new SecurityTokenDescriptor
{
Subject = Default.ClaimsIdentity,
IssuedAt = issuedAt,
NotBefore = notBefore,
Expires = expires,
};

return jsonWebTokenHandler.CreateToken(securityTokenDescriptor);
}
}
}
#nullable restore
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// 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_TokenReplayTestCases), DisableDiscoveryEnumeration = true)]
public async Task ValidateTokenAsync_TokenReplay(ValidateTokenAsyncTokenReplayTheoryData theoryData)
{
var context = TestUtilities.WriteHeader($"{this}.ValidateTokenAsync_TokenReplay", theoryData);

string jwtString = CreateTokenForTokenReplayValidation(theoryData.TokenHasExpiration);

await ValidateAndCompareResults(jwtString, theoryData, context);

TestUtilities.AssertFailIfErrors(context);
}

public static TheoryData<ValidateTokenAsyncTokenReplayTheoryData> ValidateTokenAsync_TokenReplayTestCases
{
get
{
var successfulTokenReplayCache = new TokenReplayCache
{
OnAddReturnValue = true,
OnFindReturnValue = false,
};

var failToAddTokenReplayCache = new TokenReplayCache
{
OnAddReturnValue = false,
OnFindReturnValue = false,
};

var tokenAlreadySavedTokenReplayCache = new TokenReplayCache
{
OnAddReturnValue = true,
OnFindReturnValue = true,
};

var theoryData = new TheoryData<ValidateTokenAsyncTokenReplayTheoryData>();

theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Valid_TokenHasNotBeenReplayed")
{
TokenValidationParameters = CreateTokenValidationParameters(successfulTokenReplayCache),
ValidationParameters = CreateValidationParameters(successfulTokenReplayCache),
});

theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Valid_TokenHasNoExpiration_TokenReplayCacheIsNull")
{
TokenHasExpiration = false,
TokenValidationParameters = CreateTokenValidationParameters(null),
ValidationParameters = CreateValidationParameters(null),
});

theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Invalid_TokenHasNoExpiration_TokenReplayCacheIsNotNull")
{
TokenHasExpiration = false,
TokenValidationParameters = CreateTokenValidationParameters(successfulTokenReplayCache),
ValidationParameters = CreateValidationParameters(successfulTokenReplayCache),
ExpectedIsValid = false,
ExpectedException = ExpectedException.SecurityTokenNoExpirationException("IDX10227:"),
});

theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Invalid_TokenCouldNotBeAdded")
{
TokenValidationParameters = CreateTokenValidationParameters(failToAddTokenReplayCache),
ValidationParameters = CreateValidationParameters(failToAddTokenReplayCache),
ExpectedIsValid = false,
ExpectedException = ExpectedException.SecurityTokenReplayAddFailedException("IDX10229:"),
});

theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Invalid_TokenHasBeenReplayed")
{
TokenValidationParameters = CreateTokenValidationParameters(tokenAlreadySavedTokenReplayCache),
ValidationParameters = CreateValidationParameters(tokenAlreadySavedTokenReplayCache),
ExpectedIsValid = false,
ExpectedException = ExpectedException.SecurityTokenReplayDetectedException("IDX10228:"),
});

return theoryData;

static TokenValidationParameters CreateTokenValidationParameters(ITokenReplayCache? tokenReplayCache)
{
// only validate that the token has not been replayed
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateLifetime = false,
ValidateTokenReplay = true,
ValidateIssuerSigningKey = false,
RequireSignedTokens = false,
TokenReplayCache = tokenReplayCache
};

return tokenValidationParameters;
}

static ValidationParameters CreateValidationParameters(ITokenReplayCache? tokenReplayCache)
{
ValidationParameters validationParameters = new ValidationParameters();
validationParameters.TokenReplayCache = tokenReplayCache;

// Skip all validations except token replay
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.TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation;

return validationParameters;
}
}
}

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

public bool TokenHasExpiration { get; set; } = true;
}

private static string CreateTokenForTokenReplayValidation(bool hasExpiration = true)
{
JsonWebTokenHandler jsonWebTokenHandler = new JsonWebTokenHandler();
// If the token has expiration, we use the default times.
jsonWebTokenHandler.SetDefaultTimesOnTokenCreation = hasExpiration;

SecurityTokenDescriptor securityTokenDescriptor;

if (!hasExpiration)
{
securityTokenDescriptor = new SecurityTokenDescriptor
{
Subject = Default.ClaimsIdentity,
Expires = null,
NotBefore = null,
IssuedAt = null,
};
}
else
{
securityTokenDescriptor = new SecurityTokenDescriptor
{
Subject = Default.ClaimsIdentity,
};
}

return jsonWebTokenHandler.CreateToken(securityTokenDescriptor);
}
}
}
#nullable restore
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public static TheoryData<TokenReplayTheoryData> TokenReplayValidationTestCases
new TokenReplayTheoryData
{
TestId = "Invalid_ReplayCacheIsPresent_ExpirationTimeIsNull",
ExpectedException = ExpectedException.SecurityTokenReplayDetected("IDX10227:"),
ExpectedException = ExpectedException.SecurityTokenNoExpirationException("IDX10227:"),
ExpirationTime = null,
SecurityToken = "token",
ValidationParameters = new ValidationParameters
Expand Down

0 comments on commit 788a7a4

Please sign in to comment.