diff --git a/build/dependencies.props b/build/dependencies.props index 399aac04da..79eac92835 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,6 +3,7 @@ 2.1.1 3.3.4 + 8.0.1 4.5.0 1.0.0 2.0.3 diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/Microsoft.IdentityModel.JsonWebTokens.csproj b/src/Microsoft.IdentityModel.JsonWebTokens/Microsoft.IdentityModel.JsonWebTokens.csproj index 2700c3485c..9d95cf7afe 100644 --- a/src/Microsoft.IdentityModel.JsonWebTokens/Microsoft.IdentityModel.JsonWebTokens.csproj +++ b/src/Microsoft.IdentityModel.JsonWebTokens/Microsoft.IdentityModel.JsonWebTokens.csproj @@ -28,6 +28,10 @@ + + + + diff --git a/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj b/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj index c11aa73310..6636af5a56 100644 --- a/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj +++ b/src/Microsoft.IdentityModel.Tokens/Microsoft.IdentityModel.Tokens.csproj @@ -57,4 +57,8 @@ + + + + diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs index a44b9700a2..048a371776 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/ValidationParameters.cs @@ -83,6 +83,7 @@ protected ValidationParameters(ValidationParameters other) RoleClaimTypeRetriever = other.RoleClaimTypeRetriever; SaveSigninToken = other.SaveSigninToken; SignatureValidator = other.SignatureValidator; + TimeProvider = other.TimeProvider; TokenDecryptionKeyResolver = other.TokenDecryptionKeyResolver; TokenDecryptionKeys = other.TokenDecryptionKeys; TokenReplayCache = other.TokenReplayCache; @@ -446,6 +447,11 @@ public SignatureValidatorDelegate SignatureValidator set { _signatureValidator = value; } } + /// + /// Gets or sets the time provider. + /// + internal TimeProvider TimeProvider { get; set; } = TimeProvider.System; + /// /// Gets or sets a delegate that will be called to retrieve a used for decryption. /// diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs index e5a8162370..3ccc81b6d8 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.IssuerSigningKey.cs @@ -87,7 +87,7 @@ internal static ValidationResult ValidateIssuerSign CallContext? callContext) #pragma warning restore CA1801 // Review unused parameters { - DateTime utcNow = DateTime.UtcNow; + DateTime utcNow = validationParameters.TimeProvider.GetUtcNow().UtcDateTime; DateTime? notBeforeUtc = null; DateTime? notAfterUtc = null; X509SecurityKey? x509SecurityKey = securityKey as X509SecurityKey; diff --git a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs index c4c8aef883..ce1461bf8c 100644 --- a/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs +++ b/src/Microsoft.IdentityModel.Tokens/Validation/Validators.Lifetime.cs @@ -79,7 +79,7 @@ internal static ValidationResult ValidateLifetime( new StackFrame(true), new(NotBeforeDate: notBefore, ExpirationDate: expires)); - DateTime utcNow = DateTime.UtcNow; + DateTime utcNow = validationParameters.TimeProvider.GetUtcNow().UtcDateTime; if (notBefore.HasValue && (notBefore.Value > DateTimeUtil.Add(utcNow, validationParameters.ClockSkew))) return new LifetimeValidationError( new MessageDetail( diff --git a/test/Microsoft.IdentityModel.TestUtils/MockTimeProvider.cs b/test/Microsoft.IdentityModel.TestUtils/MockTimeProvider.cs new file mode 100644 index 0000000000..36f3dcea2a --- /dev/null +++ b/test/Microsoft.IdentityModel.TestUtils/MockTimeProvider.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; + +namespace Microsoft.IdentityModel.TestUtils +{ + internal class MockTimeProvider : TimeProvider + { + // always return 09/16/2024 00:00:00:00 + public override DateTimeOffset GetUtcNow() => new DateTimeOffset(2024, 9, 16, 0, 0, 0, new(0)); + } +} diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/LifetimeValidationResultTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/LifetimeValidationResultTests.cs index 814935c9b8..3f6c211375 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/LifetimeValidationResultTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/LifetimeValidationResultTests.cs @@ -50,16 +50,17 @@ public static TheoryData ValidateLifetimeTestCases { get { - DateTime now = DateTime.UtcNow; - DateTime oneHourFromNow = DateTime.UtcNow.AddHours(1); - DateTime twoHoursFromNow = DateTime.UtcNow.AddHours(2); - DateTime twoMinutesFromNow = DateTime.UtcNow.AddMinutes(2); - DateTime sixMinutesFromNow = DateTime.UtcNow.AddMinutes(6); - DateTime oneHourAgo = DateTime.UtcNow.AddHours(-1); - DateTime twoHoursAgo = DateTime.UtcNow.AddHours(-2); - DateTime twoMinutesAgo = DateTime.UtcNow.AddMinutes(-2); - DateTime oneMinuteAgo = DateTime.UtcNow.AddMinutes(-1); - DateTime sixMinutesAgo = DateTime.UtcNow.AddMinutes(-6); + MockTimeProvider timeProvider = new MockTimeProvider(); + DateTime utcNow = timeProvider.GetUtcNow().UtcDateTime; + DateTime oneHourFromNow = utcNow.AddHours(1); + DateTime twoHoursFromNow = utcNow.AddHours(2); + DateTime twoMinutesFromNow = utcNow.AddMinutes(2); + DateTime sixMinutesFromNow = utcNow.AddMinutes(6); + DateTime oneHourAgo = utcNow.AddHours(-1); + DateTime twoHoursAgo = utcNow.AddHours(-2); + DateTime twoMinutesAgo = utcNow.AddMinutes(-2); + DateTime oneMinuteAgo = utcNow.AddMinutes(-1); + DateTime sixMinutesAgo = utcNow.AddMinutes(-6); return new TheoryData { @@ -68,27 +69,33 @@ public static TheoryData ValidateLifetimeTestCases Expires = oneHourFromNow, NotBefore = oneHourAgo, Result = new ValidatedLifetime(oneHourAgo, oneHourFromNow), - ValidationParameters = new ValidationParameters() + ValidationParameters = new ValidationParameters(){TimeProvider = timeProvider } }, new ValidateLifetimeTheoryData("Valid_NotBeforeIsNull") { Expires = oneHourFromNow, NotBefore = null, Result = new ValidatedLifetime(null, oneHourFromNow), - ValidationParameters = new ValidationParameters() + ValidationParameters = new ValidationParameters(){ TimeProvider = timeProvider } }, new ValidateLifetimeTheoryData("Valid_SkewForward") { Expires = oneHourFromNow, NotBefore = twoMinutesFromNow, - ValidationParameters = new ValidationParameters { ClockSkew = TimeSpan.FromMinutes(5) }, + ValidationParameters = new ValidationParameters { + ClockSkew = TimeSpan.FromMinutes(5), + TimeProvider = timeProvider + }, Result = new ValidatedLifetime(twoMinutesFromNow, oneHourFromNow), }, new ValidateLifetimeTheoryData("Valid_SkewBackward") { Expires = oneMinuteAgo, NotBefore = twoMinutesAgo, - ValidationParameters = new ValidationParameters { ClockSkew = TimeSpan.FromMinutes(5) }, + ValidationParameters = new ValidationParameters { + ClockSkew = TimeSpan.FromMinutes(5), + TimeProvider = timeProvider + }, Result = new ValidatedLifetime(twoMinutesAgo, oneMinuteAgo), }, new ValidateLifetimeTheoryData("Invalid_ValidationParametersIsNull") @@ -107,7 +114,7 @@ public static TheoryData ValidateLifetimeTestCases { ExpectedException = ExpectedException.SecurityTokenNoExpirationException("IDX10225:"), NotBefore = oneHourAgo, - ValidationParameters = new ValidationParameters(), + ValidationParameters = new ValidationParameters() { TimeProvider = timeProvider }, Result = new ValidationError( new MessageDetail(LogMessages.IDX10225, "null"), ValidationFailureType.LifetimeValidationFailed, @@ -119,7 +126,7 @@ public static TheoryData ValidateLifetimeTestCases ExpectedException = ExpectedException.SecurityTokenInvalidLifetimeException("IDX10224:"), Expires = oneHourAgo, NotBefore = oneHourFromNow, - ValidationParameters = new ValidationParameters(), + ValidationParameters = new ValidationParameters() { TimeProvider = timeProvider }, Result = new ValidationError( new MessageDetail( LogMessages.IDX10224, @@ -134,12 +141,12 @@ public static TheoryData ValidateLifetimeTestCases ExpectedException = ExpectedException.SecurityTokenNotYetValidException("IDX10222:"), Expires = twoHoursFromNow, NotBefore = oneHourFromNow, - ValidationParameters = new ValidationParameters(), + ValidationParameters = new ValidationParameters() { TimeProvider = timeProvider }, Result = new ValidationError( new MessageDetail( LogMessages.IDX10222, LogHelper.MarkAsNonPII(oneHourFromNow), - LogHelper.MarkAsNonPII(now)), + LogHelper.MarkAsNonPII(utcNow)), ValidationFailureType.LifetimeValidationFailed, typeof(SecurityTokenNotYetValidException), null), @@ -149,12 +156,12 @@ public static TheoryData ValidateLifetimeTestCases ExpectedException = ExpectedException.SecurityTokenExpiredException("IDX10223:"), Expires = oneHourAgo, NotBefore = twoHoursAgo, - ValidationParameters = new ValidationParameters(), + ValidationParameters = new ValidationParameters() { TimeProvider = timeProvider }, Result = new ValidationError( new MessageDetail( LogMessages.IDX10223, LogHelper.MarkAsNonPII(oneHourAgo), - LogHelper.MarkAsNonPII(now)), + LogHelper.MarkAsNonPII(utcNow)), ValidationFailureType.LifetimeValidationFailed, typeof(SecurityTokenExpiredException), null), @@ -164,12 +171,15 @@ public static TheoryData ValidateLifetimeTestCases ExpectedException = ExpectedException.SecurityTokenNotYetValidException("IDX10222:"), Expires = oneHourFromNow, NotBefore = sixMinutesFromNow, - ValidationParameters = new ValidationParameters { ClockSkew = TimeSpan.FromMinutes(5) }, + ValidationParameters = new ValidationParameters { + ClockSkew = TimeSpan.FromMinutes(5), + TimeProvider = timeProvider + }, Result = new ValidationError( new MessageDetail( LogMessages.IDX10222, LogHelper.MarkAsNonPII(sixMinutesFromNow), - LogHelper.MarkAsNonPII(now)), + LogHelper.MarkAsNonPII(utcNow)), ValidationFailureType.LifetimeValidationFailed, typeof(SecurityTokenNotYetValidException), null), @@ -179,12 +189,15 @@ public static TheoryData ValidateLifetimeTestCases ExpectedException = ExpectedException.SecurityTokenExpiredException("IDX10223:"), Expires = sixMinutesAgo, NotBefore = twoHoursAgo, - ValidationParameters = new ValidationParameters { ClockSkew = TimeSpan.FromMinutes(5) }, + ValidationParameters = new ValidationParameters { + ClockSkew = TimeSpan.FromMinutes(5), + TimeProvider = timeProvider + }, Result = new ValidationError( new MessageDetail( LogMessages.IDX10223, LogHelper.MarkAsNonPII(sixMinutesAgo), - LogHelper.MarkAsNonPII(now)), + LogHelper.MarkAsNonPII(utcNow)), ValidationFailureType.LifetimeValidationFailed, typeof(SecurityTokenExpiredException), null), diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/SigningKeyValidationResultTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/SigningKeyValidationResultTests.cs index a12a973189..216f23139f 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/SigningKeyValidationResultTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/SigningKeyValidationResultTests.cs @@ -51,7 +51,8 @@ public static TheoryData SigningKeyValidationTes { get { - DateTime utcNow = DateTime.UtcNow; + MockTimeProvider timeProvider = new MockTimeProvider(); + DateTime utcNow = timeProvider.GetUtcNow().UtcDateTime; DateTime utcExpired = KeyingMaterial.ExpiredX509SecurityKey_Public.Certificate.NotAfter.ToUniversalTime(); DateTime utcNotYetValid = KeyingMaterial.NotYetValidX509SecurityKey_Public.Certificate.NotBefore.ToUniversalTime(); @@ -62,7 +63,7 @@ public static TheoryData SigningKeyValidationTes TestId = "Valid_SecurityTokenIsPresent", SecurityKey = KeyingMaterial.SymmetricSecurityKey2_256, SecurityToken = new JwtSecurityToken(), - ValidationParameters = new ValidationParameters(), + ValidationParameters = new ValidationParameters(){ TimeProvider = timeProvider }, Result = new ValidatedSigningKeyLifetime(null, null, utcNow) }, new SigningKeyValidationTheoryData @@ -71,7 +72,7 @@ public static TheoryData SigningKeyValidationTes ExpectedException = ExpectedException.SecurityTokenArgumentNullException(substringExpected: "IDX10253:"), SecurityKey = null, SecurityToken = new JwtSecurityToken(), - ValidationParameters = new ValidationParameters(), + ValidationParameters = new ValidationParameters(){ TimeProvider = timeProvider }, Result = new ValidationError( new MessageDetail(LogMessages.IDX10253), ValidationFailureType.SigningKeyValidationFailed, @@ -84,7 +85,7 @@ public static TheoryData SigningKeyValidationTes ExpectedException = ExpectedException.SecurityTokenArgumentNullException(substringExpected: "IDX10000:"), SecurityKey = KeyingMaterial.SymmetricSecurityKey2_256, SecurityToken = null, - ValidationParameters = new ValidationParameters (), + ValidationParameters = new ValidationParameters() { TimeProvider = timeProvider }, Result = new ValidationError( new MessageDetail( LogMessages.IDX10000, @@ -114,7 +115,7 @@ public static TheoryData SigningKeyValidationTes ExpectedException = ExpectedException.SecurityTokenInvalidSigningKeyException(substringExpected: "IDX10249:"), SecurityKey = KeyingMaterial.ExpiredX509SecurityKey_Public, SecurityToken = new JwtSecurityToken(), - ValidationParameters = new ValidationParameters (), + ValidationParameters = new ValidationParameters() { TimeProvider = timeProvider }, Result = new ValidationError( new MessageDetail( LogMessages.IDX10249, @@ -130,7 +131,7 @@ public static TheoryData SigningKeyValidationTes ExpectedException = ExpectedException.SecurityTokenInvalidSigningKeyException(substringExpected: "IDX10248:"), SecurityKey = KeyingMaterial.NotYetValidX509SecurityKey_Public, SecurityToken = new JwtSecurityToken(), - ValidationParameters = new ValidationParameters (), + ValidationParameters = new ValidationParameters() { TimeProvider = timeProvider }, Result = new ValidationError( new MessageDetail( LogMessages.IDX10248, @@ -146,7 +147,7 @@ public static TheoryData SigningKeyValidationTes ExpectedException = ExpectedException.SecurityTokenArgumentNullException("IDX10253:"), SecurityKey = null, SecurityToken = new JwtSecurityToken(), - ValidationParameters = new ValidationParameters (), + ValidationParameters = new ValidationParameters() { TimeProvider = timeProvider }, Result = new ValidationError( new MessageDetail(LogMessages.IDX10253), ValidationFailureType.SigningKeyValidationFailed, diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/ValidationParametersTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/ValidationParametersTests.cs index 1ae3383d3d..e9f463cc46 100644 --- a/test/Microsoft.IdentityModel.Tokens.Tests/Validation/ValidationParametersTests.cs +++ b/test/Microsoft.IdentityModel.Tokens.Tests/Validation/ValidationParametersTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using Xunit; +using Microsoft.IdentityModel.TestUtils; namespace Microsoft.IdentityModel.Tokens.Tests.Validation { @@ -46,5 +47,25 @@ public void ValidTypes_Get_ReturnsEmptyList() Assert.Empty(validationParameters.ValidTypes); Assert.True(validationParameters.ValidTypes is IList); } + + [Fact] + public void Valid_Set_TimeProvider() + { + TimeProvider timeProvider = new MockTimeProvider(); + var validationParameters = new ValidationParameters() + { + TimeProvider = timeProvider + }; + + Assert.Equal(validationParameters.TimeProvider, timeProvider); + } + + [Fact] + public void Valid_NotNull_TimeProvider() + { + var validationParameters = new ValidationParameters(); + + Assert.NotNull(validationParameters.TimeProvider); + } } }