diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index cd8db35b..c1480e63 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -61,11 +61,16 @@ namespace OpenIddict.Core /// /// The token type. /// The subject associated with the token. + /// The date on which the token will start to be considered valid. + /// The date on which the token will no longer be considered valid. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, whose result returns the token. /// - public virtual Task CreateAsync([NotNull] string type, [NotNull] string subject, CancellationToken cancellationToken) + public virtual Task CreateAsync( + [NotNull] string type, [NotNull] string subject, + [CanBeNull] DateTimeOffset? start, + [CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(type)) { @@ -77,7 +82,7 @@ namespace OpenIddict.Core throw new ArgumentException("The subject cannot be null or empty."); } - return Store.CreateAsync(type, subject, cancellationToken); + return Store.CreateAsync(type, subject, start, end, cancellationToken); } /// @@ -87,13 +92,15 @@ namespace OpenIddict.Core /// The subject associated with the token. /// The hash of the crypto-secure random identifier associated with the token. /// The ciphertext associated with the token. + /// The date on which the token will start to be considered valid. + /// The date on which the token will no longer be considered valid. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, whose result returns the token. /// public virtual Task CreateAsync( - [NotNull] string type, [NotNull] string subject, [NotNull] string hash, - [NotNull] string ciphertext, CancellationToken cancellationToken) + [NotNull] string type, [NotNull] string subject, [NotNull] string hash, [NotNull] string ciphertext, + [CanBeNull] DateTimeOffset? start, [CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(type)) { @@ -110,7 +117,7 @@ namespace OpenIddict.Core throw new ArgumentException("The ciphertext cannot be null or empty.", nameof(ciphertext)); } - return Store.CreateAsync(type, subject, hash, ciphertext, cancellationToken); + return Store.CreateAsync(type, subject, hash, ciphertext, start, end, cancellationToken); } /// diff --git a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs index 9840206c..ca9dc883 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs @@ -4,6 +4,7 @@ * the license and the contributors participating to this project. */ +using System; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -31,11 +32,16 @@ namespace OpenIddict.Core /// /// The token type. /// The subject associated with the token. + /// The date on which the token will start to be considered valid. + /// The date on which the token will no longer be considered valid. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, whose result returns the token. /// - Task CreateAsync([NotNull] string type, [NotNull] string subject, CancellationToken cancellationToken); + Task CreateAsync( + [NotNull] string type, [NotNull] string subject, + [CanBeNull] DateTimeOffset? start, + [CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken); /// /// Creates a new reference token, which is associated with a particular subject. @@ -44,13 +50,15 @@ namespace OpenIddict.Core /// The subject associated with the token. /// The hash of the crypto-secure random identifier associated with the token. /// The ciphertext associated with the token. + /// The date on which the token will start to be considered valid. + /// The date on which the token will no longer be considered valid. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, whose result returns the token. /// Task CreateAsync( - [NotNull] string type, [NotNull] string subject, [NotNull] string hash, - [NotNull] string ciphertext, CancellationToken cancellationToken); + [NotNull] string type, [NotNull] string subject, [NotNull] string hash, [NotNull] string ciphertext, + [CanBeNull] DateTimeOffset? start, [CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken); /// /// Removes a token. diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs index d9a922a8..399a8189 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs @@ -114,11 +114,16 @@ namespace OpenIddict.EntityFrameworkCore /// /// The token type. /// The subject associated with the token. + /// The date on which the token will start to be considered valid. + /// The date on which the token will no longer be considered valid. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, whose result returns the token. /// - public virtual Task CreateAsync([NotNull] string type, [NotNull] string subject, CancellationToken cancellationToken) + public virtual Task CreateAsync( + [NotNull] string type, [NotNull] string subject, + [CanBeNull] DateTimeOffset? start, + [CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(type)) { @@ -132,6 +137,8 @@ namespace OpenIddict.EntityFrameworkCore var token = new TToken { + End = end, + Start = start, Subject = subject, Type = type }; @@ -146,13 +153,15 @@ namespace OpenIddict.EntityFrameworkCore /// The subject associated with the token. /// The hash of the crypto-secure random identifier associated with the token. /// The ciphertext associated with the token. + /// The date on which the token will start to be considered valid. + /// The date on which the token will no longer be considered valid. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, whose result returns the token. /// public virtual Task CreateAsync( - [NotNull] string type, [NotNull] string subject, [NotNull] string hash, - [NotNull] string ciphertext, CancellationToken cancellationToken) + [NotNull] string type, [NotNull] string subject, [NotNull] string hash, [NotNull] string ciphertext, + [CanBeNull] DateTimeOffset? start, [CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(type)) { @@ -167,7 +176,9 @@ namespace OpenIddict.EntityFrameworkCore var token = new TToken { Ciphertext = ciphertext, + End = end, Hash = hash, + Start = start, Subject = subject, Type = type }; diff --git a/src/OpenIddict.Models/OpenIddictToken.cs b/src/OpenIddict.Models/OpenIddictToken.cs index ba6113c0..f9926574 100644 --- a/src/OpenIddict.Models/OpenIddictToken.cs +++ b/src/OpenIddict.Models/OpenIddictToken.cs @@ -50,6 +50,12 @@ namespace OpenIddict.Models /// public virtual string Ciphertext { get; set; } + /// + /// Gets or sets the date on which the token + /// will no longer be considered valid. + /// + public virtual DateTimeOffset? End { get; set; } + /// /// Gets or sets the hashed identifier associated /// with the current token, if applicable. @@ -63,6 +69,12 @@ namespace OpenIddict.Models /// public virtual TKey Id { get; set; } + /// + /// Gets or sets the date on which the token + /// will start to be considered valid. + /// + public virtual DateTimeOffset? Start { get; set; } + /// /// Gets or sets the status of the current token. /// diff --git a/src/OpenIddict/OpenIddictProvider.Serialization.cs b/src/OpenIddict/OpenIddictProvider.Serialization.cs index 4ae3abbc..6133e488 100644 --- a/src/OpenIddict/OpenIddictProvider.Serialization.cs +++ b/src/OpenIddict/OpenIddictProvider.Serialization.cs @@ -14,6 +14,7 @@ using AspNet.Security.OpenIdConnect.Primitives; using AspNet.Security.OpenIdConnect.Server; using JetBrains.Annotations; using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; using OpenIddict.Core; @@ -31,8 +32,9 @@ namespace OpenIddict return; } - var ticket = await ReceiveTokenAsync(context.AccessToken, options, context.Request, - context.DataFormat, context.HttpContext.RequestAborted); + var ticket = await ReceiveTokenAsync( + context.AccessToken, options, context.HttpContext, + context.Request, context.DataFormat); // If a valid ticket was returned by ReceiveTokenAsync(), // force the OpenID Connect server middleware to use it. @@ -54,8 +56,9 @@ namespace OpenIddict return; } - var ticket = await ReceiveTokenAsync(context.AuthorizationCode, options, context.Request, - context.DataFormat, context.HttpContext.RequestAborted); + var ticket = await ReceiveTokenAsync( + context.AuthorizationCode, options, context.HttpContext, + context.Request, context.DataFormat); // If a valid ticket was returned by ReceiveTokenAsync(), // force the OpenID Connect server middleware to use it. @@ -77,8 +80,9 @@ namespace OpenIddict return; } - var ticket = await ReceiveTokenAsync(context.RefreshToken, options, context.Request, - context.DataFormat, context.HttpContext.RequestAborted); + var ticket = await ReceiveTokenAsync( + context.RefreshToken, options, context.HttpContext, + context.Request, context.DataFormat); // If a valid ticket was returned by ReceiveTokenAsync(), // force the OpenID Connect server middleware to use it. @@ -94,9 +98,10 @@ namespace OpenIddict public override async Task SerializeAccessToken([NotNull] SerializeAccessTokenContext context) { - var token = await CreateTokenAsync(OpenIdConnectConstants.TokenUsages.AccessToken, - (OpenIddictOptions) context.Options, context.Request, context.DataFormat, - context.Ticket, context.HttpContext.RequestAborted); + var token = await CreateTokenAsync( + OpenIdConnectConstants.TokenUsages.AccessToken, + context.Ticket, (OpenIddictOptions) context.Options, + context.HttpContext, context.Request, context.DataFormat); // If a reference token was returned by CreateTokenAsync(), // force the OpenID Connect server middleware to use it. @@ -112,9 +117,10 @@ namespace OpenIddict public override async Task SerializeAuthorizationCode([NotNull] SerializeAuthorizationCodeContext context) { - var token = await CreateTokenAsync(OpenIdConnectConstants.TokenUsages.AuthorizationCode, - (OpenIddictOptions) context.Options, context.Request, context.DataFormat, - context.Ticket, context.HttpContext.RequestAborted); + var token = await CreateTokenAsync( + OpenIdConnectConstants.TokenUsages.AuthorizationCode, + context.Ticket, (OpenIddictOptions) context.Options, + context.HttpContext, context.Request, context.DataFormat); // If a reference token was returned by CreateTokenAsync(), // force the OpenID Connect server middleware to use it. @@ -130,9 +136,10 @@ namespace OpenIddict public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context) { - var token = await CreateTokenAsync(OpenIdConnectConstants.TokenUsages.RefreshToken, - (OpenIddictOptions) context.Options, context.Request, context.DataFormat, - context.Ticket, context.HttpContext.RequestAborted); + var token = await CreateTokenAsync( + OpenIdConnectConstants.TokenUsages.RefreshToken, + context.Ticket, (OpenIddictOptions) context.Options, + context.HttpContext, context.Request, context.DataFormat); // If a reference token was returned by CreateTokenAsync(), // force the OpenID Connect server middleware to use it. @@ -147,10 +154,10 @@ namespace OpenIddict } private async Task CreateTokenAsync( - [NotNull] string type, [NotNull] OpenIddictOptions options, + [NotNull] string type, [NotNull] AuthenticationTicket ticket, + [NotNull] OpenIddictOptions options, [NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request, - [NotNull] ISecureDataFormat format, - [NotNull] AuthenticationTicket ticket, CancellationToken cancellationToken) + [NotNull] ISecureDataFormat format) { Debug.Assert(!(options.DisableTokenRevocation && options.UseReferenceTokens), "Token revocation cannot be disabled when using reference tokens."); @@ -202,14 +209,18 @@ namespace OpenIddict hash = Convert.ToBase64String(algorithm.ComputeHash(bytes)); } - token = await Tokens.CreateAsync(type, subject, hash, ciphertext, cancellationToken); + token = await Tokens.CreateAsync(type, subject, hash, ciphertext, + ticket.Properties.IssuedUtc, + ticket.Properties.ExpiresUtc, context.RequestAborted); } // Otherwise, only create a token metadata entry for authorization codes and refresh tokens. else if (string.Equals(type, OpenIdConnectConstants.TokenUsages.AuthorizationCode, StringComparison.OrdinalIgnoreCase) || string.Equals(type, OpenIdConnectConstants.TokenUsages.RefreshToken, StringComparison.OrdinalIgnoreCase)) { - token = await Tokens.CreateAsync(type, subject, cancellationToken); + token = await Tokens.CreateAsync(type, subject, + ticket.Properties.IssuedUtc, + ticket.Properties.ExpiresUtc, context.RequestAborted); } else @@ -224,7 +235,7 @@ namespace OpenIddict } // Throw an exception if the token identifier can't be resolved. - var identifier = await Tokens.GetIdAsync(token, cancellationToken); + var identifier = await Tokens.GetIdAsync(token, context.RequestAborted); if (string.IsNullOrEmpty(identifier)) { throw new InvalidOperationException("The unique key associated with a refresh token cannot be null or empty."); @@ -238,22 +249,22 @@ namespace OpenIddict // If the client application is known, associate it with the token. if (!string.IsNullOrEmpty(request.ClientId)) { - var application = await Applications.FindByClientIdAsync(request.ClientId, cancellationToken); + var application = await Applications.FindByClientIdAsync(request.ClientId, context.RequestAborted); if (application == null) { throw new InvalidOperationException("The client application cannot be retrieved from the database."); } - var key = await Applications.GetIdAsync(application, cancellationToken); + var key = await Applications.GetIdAsync(application, context.RequestAborted); - await Tokens.SetClientAsync(token, key, cancellationToken); + await Tokens.SetClientAsync(token, key, context.RequestAborted); } // If an authorization identifier was specified, bind it to the token. if (ticket.HasProperty(OpenIddictConstants.Properties.AuthorizationId)) { await Tokens.SetAuthorizationAsync(token, - ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId), cancellationToken); + ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId), context.RequestAborted); } // Otherwise, create an ad-hoc authorization if the token is an authorization code. @@ -261,21 +272,21 @@ namespace OpenIddict { Debug.Assert(!string.IsNullOrEmpty(request.ClientId), "The client identifier shouldn't be null."); - var application = await Applications.FindByClientIdAsync(request.ClientId, cancellationToken); + var application = await Applications.FindByClientIdAsync(request.ClientId, context.RequestAborted); if (application == null) { throw new InvalidOperationException("The client application cannot be retrieved from the database."); } var authorization = await Authorizations.CreateAsync(subject, - await Applications.GetIdAsync(application, cancellationToken), request.GetScopes(), cancellationToken); + await Applications.GetIdAsync(application, context.RequestAborted), request.GetScopes(), context.RequestAborted); if (authorization != null) { - var key = await Authorizations.GetIdAsync(authorization, cancellationToken); + var key = await Authorizations.GetIdAsync(authorization, context.RequestAborted); ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, key); - await Tokens.SetAuthorizationAsync(token, key, cancellationToken); + await Tokens.SetAuthorizationAsync(token, key, context.RequestAborted); } } @@ -291,8 +302,8 @@ namespace OpenIddict private async Task ReceiveTokenAsync( [NotNull] string value, [NotNull] OpenIddictOptions options, - [NotNull] OpenIdConnectRequest request, - [NotNull] ISecureDataFormat format, CancellationToken cancellationToken) + [NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request, + [NotNull] ISecureDataFormat format) { if (!options.UseReferenceTokens) { @@ -319,7 +330,7 @@ namespace OpenIddict // Retrieve the token entry from the database. If it // cannot be found, assume the token is not valid. - var token = await Tokens.FindByHashAsync(hash, cancellationToken); + var token = await Tokens.FindByHashAsync(hash, context.RequestAborted); if (token == null) { Logger.LogInformation("The reference token corresponding to the '{Hash}' hashed " + @@ -328,7 +339,7 @@ namespace OpenIddict return null; } - var identifier = await Tokens.GetIdAsync(token, cancellationToken); + var identifier = await Tokens.GetIdAsync(token, context.RequestAborted); if (string.IsNullOrEmpty(identifier)) { Logger.LogWarning("The identifier associated with the received token cannot be retrieved. " + @@ -339,7 +350,7 @@ namespace OpenIddict // Extract the encrypted payload from the token. If it's null or empty, // assume the token is not a reference token and consider it as invalid. - var ciphertext = await Tokens.GetCiphertextAsync(token, cancellationToken); + var ciphertext = await Tokens.GetCiphertextAsync(token, context.RequestAborted); if (string.IsNullOrEmpty(ciphertext)) { Logger.LogWarning("The ciphertext associated with the token '{Identifier}' cannot be retrieved. " + @@ -353,7 +364,7 @@ namespace OpenIddict { Logger.LogWarning("The ciphertext associated with the token '{Identifier}' cannot be decrypted. " + "This may indicate that the token entry is corrupted or tampered.", - await Tokens.GetIdAsync(token, cancellationToken)); + await Tokens.GetIdAsync(token, context.RequestAborted)); return null; } @@ -367,7 +378,7 @@ namespace OpenIddict if (!ticket.HasProperty(OpenIddictConstants.Properties.AuthorizationId)) { ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, - await Tokens.GetAuthorizationIdAsync(token, cancellationToken)); + await Tokens.GetAuthorizationIdAsync(token, context.RequestAborted)); } Logger.LogTrace("The reference token '{Identifier}' was successfully retrieved " + diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs index 30e8e548..26a8a9f6 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -559,7 +560,10 @@ namespace OpenIddict.Tests { var token = new OpenIddictToken(); - instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", It.IsAny())) + instance.Setup(mock => mock.CreateAsync( + OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", + It.IsAny(), It.IsAny(), + It.IsAny())) .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) diff --git a/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs b/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs index 1f0d427a..6599cfa1 100644 --- a/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs +++ b/test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs @@ -1,9 +1,11 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using AspNet.Security.OpenIdConnect.Client; using AspNet.Security.OpenIdConnect.Primitives; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; @@ -43,20 +45,28 @@ namespace OpenIddict.Tests // Assert Assert.NotNull(response.AccessToken); - Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique", It.IsAny()), Times.Never()); + Mock.Get(manager).Verify(mock => mock.CreateAsync( + OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique", + It.IsAny(), It.IsAny(), + It.IsAny()), Times.Never()); } [Fact] public async Task SerializeAccessToken_ReferenceAccessTokenIsCorrectlyPersisted() { // Arrange - var token = new OpenIddictToken(); + var token = new OpenIddictToken + { + End = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero), + Start = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero), + }; var manager = CreateTokenManager(instance => { instance.Setup(mock => mock.CreateAsync( OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique", - It.IsNotNull(), It.IsNotNull(), It.IsAny())) + It.IsNotNull(), It.IsNotNull(), + token.Start, token.End, It.IsAny())) .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) @@ -68,6 +78,12 @@ namespace OpenIddict.Tests builder.Services.AddSingleton(manager); builder.UseReferenceTokens(); + + builder.Configure(options => + { + options.SystemClock = Mock.Of(mock => mock.UtcNow == token.Start.Value); + options.AccessTokenLifetime = token.End.Value - token.Start.Value; + }); }); var client = new OpenIdConnectClient(server.CreateClient()); @@ -86,7 +102,8 @@ namespace OpenIddict.Tests Mock.Get(manager).Verify(mock => mock.CreateAsync( OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique", - It.IsNotNull(), It.IsNotNull(), It.IsAny()), Times.Once()); + It.IsNotNull(), It.IsNotNull(), + token.Start, token.End, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny()), Times.Once()); } @@ -100,7 +117,9 @@ namespace OpenIddict.Tests { instance.Setup(mock => mock.CreateAsync( OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique", - It.IsNotNull(), It.IsNotNull(), It.IsAny())) + It.IsNotNull(), It.IsNotNull(), + It.IsAny(), It.IsAny(), + It.IsAny())) .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) @@ -159,7 +178,9 @@ namespace OpenIddict.Tests { instance.Setup(mock => mock.CreateAsync( OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique", - It.IsNotNull(), It.IsNotNull(), It.IsAny())) + It.IsNotNull(), It.IsNotNull(), + It.IsAny(), It.IsAny(), + It.IsAny())) .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) @@ -245,18 +266,27 @@ namespace OpenIddict.Tests // Assert Assert.NotNull(response.Code); - Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", It.IsAny()), Times.Never()); + Mock.Get(manager).Verify(mock => mock.CreateAsync( + OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", + It.IsAny(), It.IsAny(), + It.IsAny()), Times.Never()); } [Fact] public async Task SerializeAuthorizationCode_AuthorizationCodeIsCorrectlyPersisted() { // Arrange - var token = new OpenIddictToken(); + var token = new OpenIddictToken + { + End = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero), + Start = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero), + }; var manager = CreateTokenManager(instance => { - instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", It.IsAny())) + instance.Setup(mock => mock.CreateAsync( + OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", + token.Start, token.End, It.IsAny())) .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) @@ -283,6 +313,12 @@ namespace OpenIddict.Tests })); builder.Services.AddSingleton(manager); + + builder.Configure(options => + { + options.SystemClock = Mock.Of(mock => mock.UtcNow == token.Start.Value); + options.AuthorizationCodeLifetime = token.End.Value - token.Start.Value; + }); }); var client = new OpenIdConnectClient(server.CreateClient()); @@ -298,7 +334,9 @@ namespace OpenIddict.Tests // Assert Assert.NotNull(response.Code); - Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.CreateAsync( + OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", + token.Start, token.End, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny()), Times.Once()); } @@ -306,13 +344,18 @@ namespace OpenIddict.Tests public async Task SerializeAuthorizationCode_ReferenceAuthorizationCodeIsCorrectlyPersisted() { // Arrange - var token = new OpenIddictToken(); + var token = new OpenIddictToken + { + End = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero), + Start = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero), + }; var manager = CreateTokenManager(instance => { instance.Setup(mock => mock.CreateAsync( OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", - It.IsNotNull(), It.IsNotNull(), It.IsAny())) + It.IsNotNull(), It.IsNotNull(), + token.Start, token.End, It.IsAny())) .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) @@ -341,6 +384,12 @@ namespace OpenIddict.Tests builder.Services.AddSingleton(manager); builder.UseReferenceTokens(); + + builder.Configure(options => + { + options.SystemClock = Mock.Of(mock => mock.UtcNow == token.Start.Value); + options.AuthorizationCodeLifetime = token.End.Value - token.Start.Value; + }); }); var client = new OpenIdConnectClient(server.CreateClient()); @@ -356,8 +405,10 @@ namespace OpenIddict.Tests // Assert Assert.NotNull(response.Code); - Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, - "Bob le Magnifique", It.IsNotNull(), It.IsNotNull(), It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.CreateAsync( + OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", + It.IsNotNull(), It.IsNotNull(), + token.Start, token.End, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny()), Times.Once()); } @@ -369,7 +420,10 @@ namespace OpenIddict.Tests var manager = CreateTokenManager(instance => { - instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", It.IsAny())) + instance.Setup(mock => mock.CreateAsync( + OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", + It.IsAny(), It.IsAny(), + It.IsAny())) .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) @@ -428,7 +482,10 @@ namespace OpenIddict.Tests var manager = CreateTokenManager(instance => { - instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", It.IsAny())) + instance.Setup(mock => mock.CreateAsync( + OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", + It.IsAny(), It.IsAny(), + It.IsAny())) .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) @@ -525,7 +582,10 @@ namespace OpenIddict.Tests builder.Services.AddSingleton(CreateTokenManager(instance => { - instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", It.IsAny())) + instance.Setup(mock => mock.CreateAsync( + OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique", + It.IsAny(), It.IsAny(), + It.IsAny())) .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) @@ -581,18 +641,27 @@ namespace OpenIddict.Tests // Assert Assert.NotNull(response.RefreshToken); - Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", It.IsAny()), Times.Never()); + Mock.Get(manager).Verify(mock => mock.CreateAsync( + OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", + It.IsAny(), It.IsAny(), + It.IsAny()), Times.Never()); } [Fact] public async Task SerializeRefreshToken_RefreshTokenIsCorrectlyPersisted() { // Arrange - var token = new OpenIddictToken(); + var token = new OpenIddictToken + { + End = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero), + Start = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero) + }; var manager = CreateTokenManager(instance => { - instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", It.IsAny())) + instance.Setup(mock => mock.CreateAsync( + OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", + token.Start, token.End, It.IsAny())) .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) @@ -602,6 +671,12 @@ namespace OpenIddict.Tests var server = CreateAuthorizationServer(builder => { builder.Services.AddSingleton(manager); + + builder.Configure(options => + { + options.SystemClock = Mock.Of(mock => mock.UtcNow == token.Start.Value); + options.RefreshTokenLifetime = token.End.Value - token.Start.Value; + }); }); var client = new OpenIdConnectClient(server.CreateClient()); @@ -618,7 +693,9 @@ namespace OpenIddict.Tests // Assert Assert.NotNull(response.RefreshToken); - Mock.Get(manager).Verify(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", It.IsAny()), Times.Once()); + Mock.Get(manager).Verify(mock => mock.CreateAsync( + OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", + token.Start, token.End, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny()), Times.Once()); } @@ -626,13 +703,18 @@ namespace OpenIddict.Tests public async Task SerializeRefreshToken_ReferenceRefreshTokenIsCorrectlyPersisted() { // Arrange - var token = new OpenIddictToken(); + var token = new OpenIddictToken + { + End = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero), + Start = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero), + }; var manager = CreateTokenManager(instance => { instance.Setup(mock => mock.CreateAsync( OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", - It.IsNotNull(), It.IsNotNull(), It.IsAny())) + It.IsNotNull(), It.IsNotNull(), + token.Start, token.End, It.IsAny())) .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) @@ -644,6 +726,12 @@ namespace OpenIddict.Tests builder.Services.AddSingleton(manager); builder.UseReferenceTokens(); + + builder.Configure(options => + { + options.SystemClock = Mock.Of(mock => mock.UtcNow == token.Start.Value); + options.RefreshTokenLifetime = token.End.Value - token.Start.Value; + }); }); var client = new OpenIdConnectClient(server.CreateClient()); @@ -662,7 +750,8 @@ namespace OpenIddict.Tests Mock.Get(manager).Verify(mock => mock.CreateAsync( OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", - It.IsNotNull(), It.IsNotNull(), It.IsAny()), Times.Once()); + It.IsNotNull(), It.IsNotNull(), + token.Start, token.End, It.IsAny()), Times.Once()); Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny()), Times.Once()); } @@ -674,7 +763,10 @@ namespace OpenIddict.Tests var manager = CreateTokenManager(instance => { - instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", It.IsAny())) + instance.Setup(mock => mock.CreateAsync( + OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", + It.IsAny(), It.IsAny(), + It.IsAny())) .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny())) @@ -729,7 +821,10 @@ namespace OpenIddict.Tests var manager = CreateTokenManager(instance => { - instance.Setup(mock => mock.CreateAsync(OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", It.IsAny())) + instance.Setup(mock => mock.CreateAsync( + OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique", + It.IsAny(), It.IsAny(), + It.IsAny())) .ReturnsAsync(token); instance.Setup(mock => mock.GetIdAsync(token, It.IsAny()))