Skip to content

Commit 788a7a4

Browse files
authored
Regression tests: Token Replay (#2931)
* 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
1 parent aed25ca commit 788a7a4

File tree

4 files changed

+169
-23
lines changed

4 files changed

+169
-23
lines changed

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System;
55
using System.Diagnostics;
6-
using Microsoft.IdentityModel.Logging;
76

87
namespace Microsoft.IdentityModel.Tokens
98
{
@@ -60,16 +59,16 @@ public static partial class Validators
6059
return new ValidationError(
6160
new MessageDetail(
6261
LogMessages.IDX10227,
63-
LogHelper.MarkAsUnsafeSecurityArtifact(securityToken, t => t.ToString())),
62+
securityToken),
6463
ValidationFailureType.TokenReplayValidationFailed,
65-
typeof(SecurityTokenReplayDetectedException),
64+
typeof(SecurityTokenNoExpirationException),
6665
new StackFrame(true));
6766

6867
if (validationParameters.TokenReplayCache.TryFind(securityToken))
6968
return new ValidationError(
7069
new MessageDetail(
7170
LogMessages.IDX10228,
72-
LogHelper.MarkAsUnsafeSecurityArtifact(securityToken, t => t.ToString())),
71+
securityToken),
7372
ValidationFailureType.TokenReplayValidationFailed,
7473
typeof(SecurityTokenReplayDetectedException),
7574
new StackFrame(true));
@@ -78,7 +77,7 @@ public static partial class Validators
7877
return new ValidationError(
7978
new MessageDetail(
8079
LogMessages.IDX10229,
81-
LogHelper.MarkAsUnsafeSecurityArtifact(securityToken, t => t.ToString())),
80+
securityToken),
8281
ValidationFailureType.TokenReplayValidationFailed,
8382
typeof(SecurityTokenReplayAddFailedException),
8483
new StackFrame(true));

test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenHandler.ValidateTokenAsyncTests.Lifetime.Extensibility.cs

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public async Task ValidateTokenAsync_Lifetime_Extensibility(ValidateTokenAsyncLi
1919
{
2020
var context = TestUtilities.WriteHeader($"{this}.{nameof(ValidateTokenAsync_Lifetime_Extensibility)}", theoryData);
2121

22-
string jwtString = CreateToken(theoryData.IssuedAt, theoryData.NotBefore, theoryData.Expires);
22+
string jwtString = CreateTokenWithLifetime(theoryData.IssuedAt, theoryData.NotBefore, theoryData.Expires);
2323
var handler = new JsonWebTokenHandler();
2424

2525
ValidationResult<ValidatedToken> validationResult;
@@ -300,22 +300,6 @@ internal override Exception GetException()
300300
return base.GetException();
301301
}
302302
}
303-
304-
private static string CreateToken(DateTime? issuedAt, DateTime? notBefore, DateTime? expires)
305-
{
306-
JsonWebTokenHandler jsonWebTokenHandler = new JsonWebTokenHandler();
307-
jsonWebTokenHandler.SetDefaultTimesOnTokenCreation = false; // Allow for null values to be passed in to validate.
308-
309-
SecurityTokenDescriptor securityTokenDescriptor = new SecurityTokenDescriptor
310-
{
311-
Subject = Default.ClaimsIdentity,
312-
IssuedAt = issuedAt,
313-
NotBefore = notBefore,
314-
Expires = expires,
315-
};
316-
317-
return jsonWebTokenHandler.CreateToken(securityTokenDescriptor);
318-
}
319303
}
320304
}
321305
#nullable restore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
#nullable enable
5+
using System.Threading.Tasks;
6+
using Microsoft.IdentityModel.TestUtils;
7+
using Microsoft.IdentityModel.Tokens;
8+
using Xunit;
9+
10+
namespace Microsoft.IdentityModel.JsonWebTokens.Tests
11+
{
12+
public partial class JsonWebTokenHandlerValidateTokenAsyncTests
13+
{
14+
[Theory, MemberData(nameof(ValidateTokenAsync_TokenReplayTestCases), DisableDiscoveryEnumeration = true)]
15+
public async Task ValidateTokenAsync_TokenReplay(ValidateTokenAsyncTokenReplayTheoryData theoryData)
16+
{
17+
var context = TestUtilities.WriteHeader($"{this}.ValidateTokenAsync_TokenReplay", theoryData);
18+
19+
string jwtString = CreateTokenForTokenReplayValidation(theoryData.TokenHasExpiration);
20+
21+
await ValidateAndCompareResults(jwtString, theoryData, context);
22+
23+
TestUtilities.AssertFailIfErrors(context);
24+
}
25+
26+
public static TheoryData<ValidateTokenAsyncTokenReplayTheoryData> ValidateTokenAsync_TokenReplayTestCases
27+
{
28+
get
29+
{
30+
var successfulTokenReplayCache = new TokenReplayCache
31+
{
32+
OnAddReturnValue = true,
33+
OnFindReturnValue = false,
34+
};
35+
36+
var failToAddTokenReplayCache = new TokenReplayCache
37+
{
38+
OnAddReturnValue = false,
39+
OnFindReturnValue = false,
40+
};
41+
42+
var tokenAlreadySavedTokenReplayCache = new TokenReplayCache
43+
{
44+
OnAddReturnValue = true,
45+
OnFindReturnValue = true,
46+
};
47+
48+
var theoryData = new TheoryData<ValidateTokenAsyncTokenReplayTheoryData>();
49+
50+
theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Valid_TokenHasNotBeenReplayed")
51+
{
52+
TokenValidationParameters = CreateTokenValidationParameters(successfulTokenReplayCache),
53+
ValidationParameters = CreateValidationParameters(successfulTokenReplayCache),
54+
});
55+
56+
theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Valid_TokenHasNoExpiration_TokenReplayCacheIsNull")
57+
{
58+
TokenHasExpiration = false,
59+
TokenValidationParameters = CreateTokenValidationParameters(null),
60+
ValidationParameters = CreateValidationParameters(null),
61+
});
62+
63+
theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Invalid_TokenHasNoExpiration_TokenReplayCacheIsNotNull")
64+
{
65+
TokenHasExpiration = false,
66+
TokenValidationParameters = CreateTokenValidationParameters(successfulTokenReplayCache),
67+
ValidationParameters = CreateValidationParameters(successfulTokenReplayCache),
68+
ExpectedIsValid = false,
69+
ExpectedException = ExpectedException.SecurityTokenNoExpirationException("IDX10227:"),
70+
});
71+
72+
theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Invalid_TokenCouldNotBeAdded")
73+
{
74+
TokenValidationParameters = CreateTokenValidationParameters(failToAddTokenReplayCache),
75+
ValidationParameters = CreateValidationParameters(failToAddTokenReplayCache),
76+
ExpectedIsValid = false,
77+
ExpectedException = ExpectedException.SecurityTokenReplayAddFailedException("IDX10229:"),
78+
});
79+
80+
theoryData.Add(new ValidateTokenAsyncTokenReplayTheoryData("Invalid_TokenHasBeenReplayed")
81+
{
82+
TokenValidationParameters = CreateTokenValidationParameters(tokenAlreadySavedTokenReplayCache),
83+
ValidationParameters = CreateValidationParameters(tokenAlreadySavedTokenReplayCache),
84+
ExpectedIsValid = false,
85+
ExpectedException = ExpectedException.SecurityTokenReplayDetectedException("IDX10228:"),
86+
});
87+
88+
return theoryData;
89+
90+
static TokenValidationParameters CreateTokenValidationParameters(ITokenReplayCache? tokenReplayCache)
91+
{
92+
// only validate that the token has not been replayed
93+
var tokenValidationParameters = new TokenValidationParameters
94+
{
95+
ValidateAudience = false,
96+
ValidateIssuer = false,
97+
ValidateLifetime = false,
98+
ValidateTokenReplay = true,
99+
ValidateIssuerSigningKey = false,
100+
RequireSignedTokens = false,
101+
TokenReplayCache = tokenReplayCache
102+
};
103+
104+
return tokenValidationParameters;
105+
}
106+
107+
static ValidationParameters CreateValidationParameters(ITokenReplayCache? tokenReplayCache)
108+
{
109+
ValidationParameters validationParameters = new ValidationParameters();
110+
validationParameters.TokenReplayCache = tokenReplayCache;
111+
112+
// Skip all validations except token replay
113+
validationParameters.AlgorithmValidator = SkipValidationDelegates.SkipAlgorithmValidation;
114+
validationParameters.AudienceValidator = SkipValidationDelegates.SkipAudienceValidation;
115+
validationParameters.IssuerSigningKeyValidator = SkipValidationDelegates.SkipIssuerSigningKeyValidation;
116+
validationParameters.IssuerValidatorAsync = SkipValidationDelegates.SkipIssuerValidation;
117+
validationParameters.LifetimeValidator = SkipValidationDelegates.SkipLifetimeValidation;
118+
validationParameters.SignatureValidator = SkipValidationDelegates.SkipSignatureValidation;
119+
validationParameters.TokenTypeValidator = SkipValidationDelegates.SkipTokenTypeValidation;
120+
121+
return validationParameters;
122+
}
123+
}
124+
}
125+
126+
public class ValidateTokenAsyncTokenReplayTheoryData : ValidateTokenAsyncBaseTheoryData
127+
{
128+
public ValidateTokenAsyncTokenReplayTheoryData(string testId) : base(testId) { }
129+
130+
public bool TokenHasExpiration { get; set; } = true;
131+
}
132+
133+
private static string CreateTokenForTokenReplayValidation(bool hasExpiration = true)
134+
{
135+
JsonWebTokenHandler jsonWebTokenHandler = new JsonWebTokenHandler();
136+
// If the token has expiration, we use the default times.
137+
jsonWebTokenHandler.SetDefaultTimesOnTokenCreation = hasExpiration;
138+
139+
SecurityTokenDescriptor securityTokenDescriptor;
140+
141+
if (!hasExpiration)
142+
{
143+
securityTokenDescriptor = new SecurityTokenDescriptor
144+
{
145+
Subject = Default.ClaimsIdentity,
146+
Expires = null,
147+
NotBefore = null,
148+
IssuedAt = null,
149+
};
150+
}
151+
else
152+
{
153+
securityTokenDescriptor = new SecurityTokenDescriptor
154+
{
155+
Subject = Default.ClaimsIdentity,
156+
};
157+
}
158+
159+
return jsonWebTokenHandler.CreateToken(securityTokenDescriptor);
160+
}
161+
}
162+
}
163+
#nullable restore

test/Microsoft.IdentityModel.Tokens.Tests/Validation/ReplayValidationResultTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public static TheoryData<TokenReplayTheoryData> TokenReplayValidationTestCases
126126
new TokenReplayTheoryData
127127
{
128128
TestId = "Invalid_ReplayCacheIsPresent_ExpirationTimeIsNull",
129-
ExpectedException = ExpectedException.SecurityTokenReplayDetected("IDX10227:"),
129+
ExpectedException = ExpectedException.SecurityTokenNoExpirationException("IDX10227:"),
130130
ExpirationTime = null,
131131
SecurityToken = "token",
132132
ValidationParameters = new ValidationParameters

0 commit comments

Comments
 (0)