-
Notifications
You must be signed in to change notification settings - Fork 414
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
Regression testing: Token type, replay, and lifetime validation. #2823
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -2,9 +2,9 @@ | |||||||
// Licensed under the MIT License. | ||||||||
|
||||||||
#if NET472_OR_GREATER || NET6_0_OR_GREATER | ||||||||
using System; | ||||||||
using Newtonsoft.Json.Linq; | ||||||||
#endif | ||||||||
using System; | ||||||||
using System.Collections.Generic; | ||||||||
using System.Security.Claims; | ||||||||
using System.Threading; | ||||||||
|
@@ -13,6 +13,8 @@ | |||||||
using Microsoft.IdentityModel.Tokens; | ||||||||
using Xunit; | ||||||||
|
||||||||
#nullable enable | ||||||||
|
||||||||
namespace Microsoft.IdentityModel.JsonWebTokens.Tests | ||||||||
{ | ||||||||
public class JsonWebTokenHandlerValidationParametersTests | ||||||||
|
@@ -23,6 +25,7 @@ public async Task ValidateTokenAsync(JsonWebTokenHandlerValidationParametersTheo | |||||||
var context = TestUtilities.WriteHeader($"{this}.ValidateTokenAsync", theoryData); | ||||||||
|
||||||||
JsonWebTokenHandler jsonWebTokenHandler = new JsonWebTokenHandler(); | ||||||||
jsonWebTokenHandler.SetDefaultTimesOnTokenCreation = theoryData.SetDefaultValuesOnTokenCreation; | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we not create the jwt when creating the theoryData? |
||||||||
|
||||||||
string jwtString; | ||||||||
|
||||||||
|
@@ -39,7 +42,11 @@ public async Task ValidateTokenAsync(JsonWebTokenHandlerValidationParametersTheo | |||||||
EncryptingCredentials = theoryData.EncryptingCredentials, | ||||||||
AdditionalHeaderClaims = theoryData.AdditionalHeaderParams, | ||||||||
Audience = theoryData.Audience, | ||||||||
Expires = theoryData.Expires, | ||||||||
IssuedAt = theoryData.IssuedAt, | ||||||||
Issuer = theoryData.Issuer, | ||||||||
NotBefore = theoryData.NotBefore, | ||||||||
TokenType = theoryData.TokenType, | ||||||||
}; | ||||||||
|
||||||||
jwtString = jsonWebTokenHandler.CreateToken(securityTokenDescriptor); | ||||||||
|
@@ -49,7 +56,7 @@ public async Task ValidateTokenAsync(JsonWebTokenHandlerValidationParametersTheo | |||||||
await jsonWebTokenHandler.ValidateTokenAsync(jwtString, theoryData.TokenValidationParameters); | ||||||||
ValidationResult<ValidatedToken> validationParametersResult = | ||||||||
await jsonWebTokenHandler.ValidateTokenAsync( | ||||||||
jwtString, theoryData.ValidationParameters, theoryData.CallContext, CancellationToken.None); | ||||||||
jwtString, theoryData.ValidationParameters!, theoryData.CallContext, CancellationToken.None); | ||||||||
|
||||||||
if (tokenValidationParametersResult.IsValid != theoryData.ExpectedIsValid) | ||||||||
context.AddDiff($"tokenValidationParametersResult.IsValid != theoryData.ExpectedIsValid"); | ||||||||
|
@@ -102,6 +109,11 @@ public static TheoryData<JsonWebTokenHandlerValidationParametersTheoryData> Json | |||||||
ValidationParameters = CreateValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey), | ||||||||
}, | ||||||||
new JsonWebTokenHandlerValidationParametersTheoryData("Invalid_ValidationParametersNull") | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add a new line between creation of theoryData. |
||||||||
{ | ||||||||
ExpectedIsValid = false, | ||||||||
ExpectedException = ExpectedException.ArgumentNullException("IDX10000:"), | ||||||||
}, | ||||||||
new JsonWebTokenHandlerValidationParametersTheoryData("Invalid_MalformedToken") | ||||||||
{ | ||||||||
TokenString = "malformedToken", | ||||||||
|
@@ -139,6 +151,111 @@ public static TheoryData<JsonWebTokenHandlerValidationParametersTheoryData> Json | |||||||
// removal of the ValidAudience property from the ValidationParameters class. | ||||||||
ExpectedExceptionValidationParameters = ExpectedException.SecurityTokenInvalidAudienceException("IDX10215:"), | ||||||||
}, | ||||||||
new JsonWebTokenHandlerValidationParametersTheoryData("Valid_WithinFiveMinutesOfExpiration") | ||||||||
{ | ||||||||
// Token expired 4 minutes ago, but the clock skew is 5 minutes. | ||||||||
TokenValidationParameters = CreateTokenValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey), | ||||||||
ValidationParameters = CreateValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey), | ||||||||
IssuedAt = DateTime.UtcNow - TimeSpan.FromHours(2), | ||||||||
NotBefore = DateTime.UtcNow - TimeSpan.FromHours(2), | ||||||||
Expires = DateTime.UtcNow - TimeSpan.FromMinutes(4), | ||||||||
}, | ||||||||
new JsonWebTokenHandlerValidationParametersTheoryData("Valid_WithinFiveMinutesOfNotBefore") | ||||||||
{ | ||||||||
// Token is not yet valid for another 4 minutes, but the clock skew is 5 minutes. | ||||||||
TokenValidationParameters = CreateTokenValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey), | ||||||||
ValidationParameters = CreateValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey), | ||||||||
IssuedAt = DateTime.UtcNow, | ||||||||
NotBefore = DateTime.UtcNow + TimeSpan.FromMinutes(4), | ||||||||
Expires = DateTime.UtcNow + TimeSpan.FromHours(2), | ||||||||
}, | ||||||||
new JsonWebTokenHandlerValidationParametersTheoryData("Invalid_Expired") | ||||||||
{ | ||||||||
TokenValidationParameters = CreateTokenValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey), | ||||||||
ValidationParameters = CreateValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey), | ||||||||
IssuedAt = DateTime.UtcNow - TimeSpan.FromDays(2), | ||||||||
NotBefore = DateTime.UtcNow - TimeSpan.FromDays(2), | ||||||||
Expires = DateTime.UtcNow - TimeSpan.FromDays(1), | ||||||||
ExpectedIsValid = false, | ||||||||
ExpectedException = ExpectedException.SecurityTokenExpiredException("IDX10223:"), | ||||||||
}, | ||||||||
new JsonWebTokenHandlerValidationParametersTheoryData("Invalid_NotYetValid") | ||||||||
{ | ||||||||
TokenValidationParameters = CreateTokenValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey), | ||||||||
ValidationParameters = CreateValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey), | ||||||||
IssuedAt = DateTime.UtcNow + TimeSpan.FromDays(1), | ||||||||
NotBefore = DateTime.UtcNow + TimeSpan.FromDays(1), | ||||||||
Expires = DateTime.UtcNow + TimeSpan.FromDays(2), | ||||||||
ExpectedIsValid = false, | ||||||||
ExpectedException = ExpectedException.SecurityTokenNotYetValidException("IDX10222:"), | ||||||||
}, | ||||||||
new JsonWebTokenHandlerValidationParametersTheoryData("Invalid_NoExpiration") | ||||||||
{ | ||||||||
TokenValidationParameters = CreateTokenValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey), | ||||||||
ValidationParameters = CreateValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey), | ||||||||
Expires = null, | ||||||||
NotBefore = DateTime.UtcNow, | ||||||||
IssuedAt = DateTime.UtcNow, | ||||||||
ExpectedIsValid = false, | ||||||||
SetDefaultValuesOnTokenCreation = false, | ||||||||
ExpectedException = ExpectedException.SecurityTokenNoExpirationException("IDX10225:"), | ||||||||
}, | ||||||||
new JsonWebTokenHandlerValidationParametersTheoryData("Invalid_InvalidLifetime") | ||||||||
{ | ||||||||
TokenValidationParameters = CreateTokenValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey), | ||||||||
ValidationParameters = CreateValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey), | ||||||||
IssuedAt = DateTime.UtcNow + TimeSpan.FromDays(2), | ||||||||
NotBefore = DateTime.UtcNow + TimeSpan.FromDays(2), | ||||||||
Expires = DateTime.UtcNow + TimeSpan.FromDays(1), | ||||||||
ExpectedIsValid = false, | ||||||||
ExpectedException = ExpectedException.SecurityTokenInvalidLifetimeException("IDX10224:"), | ||||||||
}, | ||||||||
new JsonWebTokenHandlerValidationParametersTheoryData("Invalid_TokenTypeNotSupported") | ||||||||
{ | ||||||||
// Token Type is 'JWT' but only 'none' is considered valid for this test's purposes | ||||||||
TokenValidationParameters = CreateTokenValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey, | ||||||||
validTokenTypes: ["none"]), | ||||||||
ValidationParameters = CreateValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey, | ||||||||
validTokenTypes: ["none"]), | ||||||||
ExpectedIsValid = false, | ||||||||
ExpectedException = ExpectedException.SecurityTokenInvalidTypeException("IDX10257:"), | ||||||||
}, | ||||||||
new JsonWebTokenHandlerValidationParametersTheoryData("Invalid_TokenReplayDetected_TokenFoundInCache") | ||||||||
{ | ||||||||
TokenValidationParameters = CreateTokenValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey, | ||||||||
tokenReplayCache: new TestTokenReplayCache(false, true)), | ||||||||
ValidationParameters = CreateValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey, | ||||||||
tokenReplayCache: new TestTokenReplayCache(false, true)), | ||||||||
ExpectedIsValid = false, | ||||||||
ExpectedException = ExpectedException.SecurityTokenReplayDetectedException("IDX10228:"), | ||||||||
}, | ||||||||
new JsonWebTokenHandlerValidationParametersTheoryData("Invalid_TokenReplayDetected_AddToCacheFailed") | ||||||||
{ | ||||||||
TokenValidationParameters = CreateTokenValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey, | ||||||||
tokenReplayCache: new TestTokenReplayCache(false, false)), | ||||||||
ValidationParameters = CreateValidationParameters( | ||||||||
Default.Issuer, [Default.Audience], Default.AsymmetricSigningKey, | ||||||||
tokenReplayCache: new TestTokenReplayCache(false, false)), | ||||||||
ExpectedIsValid = false, | ||||||||
ExpectedException = ExpectedException.SecurityTokenReplayAddFailedException("IDX10229:"), | ||||||||
}, | ||||||||
new JsonWebTokenHandlerValidationParametersTheoryData("Invalid_TokenNotSigned") | ||||||||
{ | ||||||||
TokenValidationParameters = CreateTokenValidationParameters( | ||||||||
|
@@ -291,11 +408,13 @@ public static TheoryData<JsonWebTokenHandlerValidationParametersTheoryData> Json | |||||||
}; | ||||||||
|
||||||||
static TokenValidationParameters CreateTokenValidationParameters( | ||||||||
string issuer, | ||||||||
List<string> audiences, | ||||||||
SecurityKey issuerSigningKey, | ||||||||
SecurityKey tokenDecryptionKey = null, | ||||||||
List<string> validAlgorithms = null, | ||||||||
string? issuer, | ||||||||
List<string>? audiences, | ||||||||
SecurityKey? issuerSigningKey, | ||||||||
SecurityKey? tokenDecryptionKey = null, | ||||||||
List<string>? validAlgorithms = null, | ||||||||
List<string>? validTokenTypes = null, | ||||||||
ITokenReplayCache? tokenReplayCache = null, | ||||||||
bool tryAllKeys = false) => new TokenValidationParameters | ||||||||
{ | ||||||||
ValidAlgorithms = validAlgorithms, | ||||||||
|
@@ -309,21 +428,27 @@ static TokenValidationParameters CreateTokenValidationParameters( | |||||||
ValidAudiences = audiences, | ||||||||
ValidIssuer = issuer, | ||||||||
TryAllIssuerSigningKeys = tryAllKeys, | ||||||||
ValidTypes = validTokenTypes, | ||||||||
TokenReplayCache = tokenReplayCache, | ||||||||
}; | ||||||||
|
||||||||
static ValidationParameters CreateValidationParameters( | ||||||||
string issuer, | ||||||||
List<string> audiences, | ||||||||
SecurityKey issuerSigningKey, | ||||||||
SecurityKey tokenDecryptionKey = null, | ||||||||
List<string> validAlgorithms = null, | ||||||||
string? issuer, | ||||||||
List<string>? audiences, | ||||||||
SecurityKey? issuerSigningKey, | ||||||||
SecurityKey? tokenDecryptionKey = null, | ||||||||
List<string>? validAlgorithms = null, | ||||||||
List<string>? validTokenTypes = null, | ||||||||
ITokenReplayCache? tokenReplayCache = null, | ||||||||
bool tryAllKeys = false) | ||||||||
{ | ||||||||
ValidationParameters validationParameters = new ValidationParameters(); | ||||||||
validationParameters.ValidIssuers.Add(issuer); | ||||||||
audiences.ForEach(audience => validationParameters.ValidAudiences.Add(audience)); | ||||||||
audiences?.ForEach(audience => validationParameters.ValidAudiences.Add(audience)); | ||||||||
validTokenTypes?.ForEach(tokenType => validationParameters.ValidTypes.Add(tokenType)); | ||||||||
validationParameters.IssuerSigningKeys.Add(issuerSigningKey); | ||||||||
validationParameters.TryAllIssuerSigningKeys = tryAllKeys; | ||||||||
validationParameters.TokenReplayCache = tokenReplayCache; | ||||||||
if (validAlgorithms is not null) | ||||||||
validationParameters.ValidAlgorithms = validAlgorithms; | ||||||||
if (tokenDecryptionKey is not null) | ||||||||
|
@@ -354,22 +479,48 @@ static Dictionary<string, object> AdditionalEcdhEsHeaderParameters(JsonWebKey pu | |||||||
} | ||||||||
} | ||||||||
|
||||||||
private class TestTokenReplayCache : ITokenReplayCache | ||||||||
{ | ||||||||
private bool _onAddReturnValue; | ||||||||
private bool _onFindReturnValue; | ||||||||
public TestTokenReplayCache(bool onAddReturnValue, bool onFindReturnValue) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
{ | ||||||||
_onAddReturnValue = onAddReturnValue; | ||||||||
_onFindReturnValue = onFindReturnValue; | ||||||||
} | ||||||||
public bool TryAdd(string securityToken, DateTime expirationTime) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
{ | ||||||||
return _onAddReturnValue; | ||||||||
} | ||||||||
|
||||||||
public bool TryFind(string securityToken) | ||||||||
{ | ||||||||
return _onFindReturnValue; | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
public class JsonWebTokenHandlerValidationParametersTheoryData : TheoryDataBase | ||||||||
{ | ||||||||
public JsonWebTokenHandlerValidationParametersTheoryData(string testId) : base(testId) { } | ||||||||
public string TokenString { get; internal set; } = null; | ||||||||
public SigningCredentials SigningCredentials { get; internal set; } = Default.AsymmetricSigningCredentials; | ||||||||
public EncryptingCredentials EncryptingCredentials { get; internal set; } | ||||||||
public IDictionary<string, object> AdditionalHeaderParams { get; internal set; } | ||||||||
public ClaimsIdentity Subject { get; internal set; } = Default.ClaimsIdentity; | ||||||||
public string? TokenString { get; internal set; } = null; | ||||||||
public bool SetDefaultValuesOnTokenCreation { get; internal set; } = true; | ||||||||
public SigningCredentials? SigningCredentials { get; internal set; } = Default.AsymmetricSigningCredentials; | ||||||||
public EncryptingCredentials? EncryptingCredentials { get; internal set; } | ||||||||
public IDictionary<string, object>? AdditionalHeaderParams { get; internal set; } | ||||||||
public ClaimsIdentity? Subject { get; internal set; } = Default.ClaimsIdentity; | ||||||||
public string Audience { get; internal set; } = Default.Audience; | ||||||||
public string Issuer { get; internal set; } = Default.Issuer; | ||||||||
public DateTime? IssuedAt { get; internal set; } | ||||||||
public DateTime? NotBefore { get; internal set; } | ||||||||
public DateTime? Expires { get; internal set; } | ||||||||
internal bool ExpectedIsValid { get; set; } = true; | ||||||||
internal TokenValidationParameters TokenValidationParameters { get; set; } | ||||||||
internal ValidationParameters ValidationParameters { get; set; } | ||||||||
public string? TokenType { get; internal set; } | ||||||||
internal TokenValidationParameters? TokenValidationParameters { get; set; } | ||||||||
internal ValidationParameters? ValidationParameters { get; set; } | ||||||||
|
||||||||
// only set if we expect a different message on this path | ||||||||
internal ExpectedException ExpectedExceptionValidationParameters { get; set; } = null; | ||||||||
internal ExpectedException? ExpectedExceptionValidationParameters { get; set; } = null; | ||||||||
} | ||||||||
} | ||||||||
} | ||||||||
#nullable restore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
be consistent with spacing around #nullable.
Other files do not have a blank line.