Browse Source

Introduce a new rolling tokens option and disable it by default

pull/454/head
Kévin Chalet 9 years ago
parent
commit
eb6588576e
  1. 25
      src/OpenIddict.Core/Descriptors/OpenIddictAuthorizationDescriptor.cs
  2. 50
      src/OpenIddict.Core/Descriptors/OpenIddictTokenDescriptor.cs
  3. 24
      src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
  4. 92
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  5. 1
      src/OpenIddict.Core/OpenIddictConstants.cs
  6. 8
      src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs
  7. 60
      src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs
  8. 23
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
  9. 132
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
  10. 14
      src/OpenIddict.Models/OpenIddictToken.cs
  11. 18
      src/OpenIddict/OpenIddictExtensions.cs
  12. 10
      src/OpenIddict/OpenIddictInitializer.cs
  13. 9
      src/OpenIddict/OpenIddictOptions.cs
  14. 33
      src/OpenIddict/OpenIddictProvider.Exchange.cs
  15. 161
      src/OpenIddict/OpenIddictProvider.Serialization.cs
  16. 46
      test/OpenIddict.Tests/OpenIddictInitializerTests.cs
  17. 8
      test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs
  18. 74
      test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs
  19. 3
      test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs
  20. 298
      test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs

25
src/OpenIddict.Core/Descriptors/OpenIddictAuthorizationDescriptor.cs

@ -0,0 +1,25 @@
using System.Collections.Generic;
namespace OpenIddict.Core
{
/// <summary>
/// Represents an OpenIddict authorization descriptor.
/// </summary>
public class OpenIddictAuthorizationDescriptor
{
/// <summary>
/// Gets or sets the application identifier associated with the authorization.
/// </summary>
public string ApplicationId { get; set; }
/// <summary>
/// Gets or sets the scopes associated with the authorization.
/// </summary>
public IEnumerable<string> Scopes { get; set; }
/// <summary>
/// Gets or sets the subject associated with the authorization.
/// </summary>
public string Subject { get; set; }
}
}

50
src/OpenIddict.Core/Descriptors/OpenIddictTokenDescriptor.cs

@ -0,0 +1,50 @@
using System;
namespace OpenIddict.Core
{
/// <summary>
/// Represents an OpenIddict token descriptor.
/// </summary>
public class OpenIddictTokenDescriptor
{
/// <summary>
/// Gets or sets the application identifier associated with the token.
/// </summary>
public string ApplicationId { get; set; }
/// <summary>
/// Gets or sets the authorization identifier associated with the token.
/// </summary>
public string AuthorizationId { get; set; }
/// <summary>
/// Gets or sets the encrypted payload associated with the token.
/// </summary>
public string Ciphertext { get; set; }
/// <summary>
/// Gets or sets the creation date associated with the token.
/// </summary>
public DateTimeOffset? CreationDate { get; set; }
/// <summary>
/// Gets or sets the expiration date associated with the token.
/// </summary>
public DateTimeOffset? ExpirationDate { get; set; }
/// <summary>
/// Gets or sets the cryptographic hash associated with the token.
/// </summary>
public string Hash { get; set; }
/// <summary>
/// Gets or sets the subject associated with the token.
/// </summary>
public string Subject { get; set; }
/// <summary>
/// Gets or sets the token type.
/// </summary>
public string Type { get; set; }
}
}

24
src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs

@ -65,33 +65,19 @@ namespace OpenIddict.Core
/// <summary>
/// Creates a new authorization.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="scopes">The scopes associated with the authorization.</param>
/// <param name="descriptor">The authorization descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the authorization.
/// </returns>
public virtual Task<TAuthorization> CreateAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] IEnumerable<string> scopes, CancellationToken cancellationToken)
public virtual Task<TAuthorization> CreateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken)
{
if (scopes == null)
{
throw new ArgumentNullException(nameof(scopes));
}
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
if (descriptor == null)
{
throw new ArgumentException("The client cannot be null or empty.", nameof(subject));
throw new ArgumentNullException(nameof(descriptor));
}
return Store.CreateAsync(subject, client, scopes, cancellationToken);
return Store.CreateAsync(descriptor, cancellationToken);
}
/// <summary>

92
src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs

@ -66,65 +66,39 @@ namespace OpenIddict.Core
/// <summary>
/// Creates a new token, which is associated with a particular subject.
/// </summary>
/// <param name="type">The token type.</param>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="start">The date on which the token will start to be considered valid.</param>
/// <param name="end">The date on which the token will no longer be considered valid.</param>
/// <param name="descriptor">The token descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the token.
/// </returns>
public virtual Task<TToken> CreateAsync(
[NotNull] string type, [NotNull] string subject,
[CanBeNull] DateTimeOffset? start,
[CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken)
public virtual Task<TToken> CreateAsync([NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(type))
if (descriptor == null)
{
throw new ArgumentException("The token type cannot be null or empty.", nameof(type));
throw new ArgumentNullException(nameof(descriptor));
}
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.");
}
return Store.CreateAsync(type, subject, start, end, cancellationToken);
return Store.CreateAsync(descriptor, cancellationToken);
}
/// <summary>
/// Creates a new reference token, which is associated with a particular subject.
/// Extends the specified token by replacing its expiration date.
/// </summary>
/// <param name="type">The token type.</param>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="hash">The hash of the crypto-secure random identifier associated with the token.</param>
/// <param name="ciphertext">The ciphertext associated with the token.</param>
/// <param name="start">The date on which the token will start to be considered valid.</param>
/// <param name="end">The date on which the token will no longer be considered valid.</param>
/// <param name="token">The token.</param>
/// <param name="date">The date on which the token will no longer be considered valid.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the token.
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task<TToken> CreateAsync(
[NotNull] string type, [NotNull] string subject, [NotNull] string hash, [NotNull] string ciphertext,
[CanBeNull] DateTimeOffset? start, [CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken)
public virtual async Task ExtendAsync([NotNull] TToken token, [CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException("The token type cannot be null or empty.", nameof(type));
}
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.");
}
if (string.IsNullOrEmpty(ciphertext))
if (token == null)
{
throw new ArgumentException("The ciphertext cannot be null or empty.", nameof(ciphertext));
throw new ArgumentNullException(nameof(token));
}
return Store.CreateAsync(type, subject, hash, ciphertext, start, end, cancellationToken);
await Store.SetExpirationDateAsync(token, date, cancellationToken);
await UpdateAsync(token, cancellationToken);
}
/// <summary>
@ -221,6 +195,44 @@ namespace OpenIddict.Core
return Store.GetCiphertextAsync(token, cancellationToken);
}
/// <summary>
/// Retrieves the creation date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the creation date associated with the specified token.
/// </returns>
public virtual Task<DateTimeOffset?> GetCreationDateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return Store.GetCreationDateAsync(token, cancellationToken);
}
/// <summary>
/// Retrieves the expiration date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the expiration date associated with the specified token.
/// </returns>
public virtual Task<DateTimeOffset?> GetExpirationDateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return Store.GetExpirationDateAsync(token, cancellationToken);
}
/// <summary>
/// Retrieves the hashed identifier associated with a token.
/// </summary>

1
src/OpenIddict.Core/OpenIddictConstants.cs

@ -33,6 +33,7 @@ namespace OpenIddict.Core
public static class Properties
{
public const string AuthorizationId = ".authorization_id";
public const string TokenId = ".token_id";
}
public static class Scopes

8
src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs

@ -36,16 +36,12 @@ namespace OpenIddict.Core
/// <summary>
/// Creates a new authorization.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="scopes">The scopes associated with the authorization.</param>
/// <param name="descriptor">The authorization descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the authorization.
/// </returns>
Task<TAuthorization> CreateAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] IEnumerable<string> scopes, CancellationToken cancellationToken);
Task<TAuthorization> CreateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken);
/// <summary>
/// Retrieves an authorization using its unique identifier.

60
src/OpenIddict.Core/Stores/IOpenIddictTokenStore.cs

@ -36,35 +36,12 @@ namespace OpenIddict.Core
/// <summary>
/// Creates a new token, which is associated with a particular subject.
/// </summary>
/// <param name="type">The token type.</param>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="start">The date on which the token will start to be considered valid.</param>
/// <param name="end">The date on which the token will no longer be considered valid.</param>
/// <param name="descriptor">The token descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the token.
/// </returns>
Task<TToken> CreateAsync(
[NotNull] string type, [NotNull] string subject,
[CanBeNull] DateTimeOffset? start,
[CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken);
/// <summary>
/// Creates a new reference token, which is associated with a particular subject.
/// </summary>
/// <param name="type">The token type.</param>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="hash">The hash of the crypto-secure random identifier associated with the token.</param>
/// <param name="ciphertext">The ciphertext associated with the token.</param>
/// <param name="start">The date on which the token will start to be considered valid.</param>
/// <param name="end">The date on which the token will no longer be considered valid.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the token.
/// </returns>
Task<TToken> CreateAsync(
[NotNull] string type, [NotNull] string subject, [NotNull] string hash, [NotNull] string ciphertext,
[CanBeNull] DateTimeOffset? start, [CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken);
Task<TToken> CreateAsync([NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken);
/// <summary>
/// Removes a token.
@ -140,6 +117,28 @@ namespace OpenIddict.Core
/// </returns>
Task<string> GetCiphertextAsync([NotNull] TToken token, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the creation date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the creation date associated with the specified token.
/// </returns>
Task<DateTimeOffset?> GetCreationDateAsync([NotNull] TToken token, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the expiration date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the expiration date associated with the specified token.
/// </returns>
Task<DateTimeOffset?> GetExpirationDateAsync([NotNull] TToken token, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the hashed identifier associated with a token.
/// </summary>
@ -217,6 +216,17 @@ namespace OpenIddict.Core
/// </returns>
Task SetClientAsync([NotNull] TToken token, [CanBeNull] string identifier, CancellationToken cancellationToken);
/// <summary>
/// Sets the expiration date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="date">The date on which the token will no longer be considered valid.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
Task SetExpirationDateAsync([NotNull] TToken token, [CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken);
/// <summary>
/// Sets the status associated with a token.
/// </summary>

23
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs

@ -113,28 +113,19 @@ namespace OpenIddict.EntityFrameworkCore
/// <summary>
/// Creates a new authorization.
/// </summary>
/// <param name="subject">The subject associated with the authorization.</param>
/// <param name="client">The client associated with the authorization.</param>
/// <param name="scopes">The scopes associated with the authorization.</param>
/// <param name="descriptor">The authorization descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the authorization.
/// </returns>
public virtual async Task<TAuthorization> CreateAsync(
[NotNull] string subject, [NotNull] string client,
[NotNull] IEnumerable<string> scopes, CancellationToken cancellationToken)
public virtual async Task<TAuthorization> CreateAsync([NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.", nameof(subject));
}
if (string.IsNullOrEmpty(client))
if (descriptor == null)
{
throw new ArgumentException("The client cannot be null or empty.", nameof(subject));
throw new ArgumentNullException(nameof(descriptor));
}
var key = ConvertIdentifierFromString(client);
var key = ConvertIdentifierFromString(descriptor.ApplicationId);
var application = await Applications.SingleOrDefaultAsync(entity => entity.Id.Equals(key));
if (application == null)
@ -145,8 +136,8 @@ namespace OpenIddict.EntityFrameworkCore
var authorization = new TAuthorization
{
Application = application,
Scope = string.Join(" ", scopes),
Subject = subject
Scope = string.Join(" ", descriptor.Scopes),
Subject = descriptor.Subject
};
return await CreateAsync(authorization, cancellationToken);

132
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs

@ -117,78 +117,51 @@ namespace OpenIddict.EntityFrameworkCore
/// <summary>
/// Creates a new token, which is associated with a particular subject.
/// </summary>
/// <param name="type">The token type.</param>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="start">The date on which the token will start to be considered valid.</param>
/// <param name="end">The date on which the token will no longer be considered valid.</param>
/// <param name="descriptor">The token descriptor.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the token.
/// </returns>
public virtual Task<TToken> CreateAsync(
[NotNull] string type, [NotNull] string subject,
[CanBeNull] DateTimeOffset? start,
[CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken)
public virtual async Task<TToken> CreateAsync([NotNull] OpenIddictTokenDescriptor descriptor, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(type))
if (descriptor == null)
{
throw new ArgumentException("The token type cannot be null or empty.");
}
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException("The subject cannot be null or empty.");
throw new ArgumentNullException(nameof(descriptor));
}
var token = new TToken
{
End = end,
Start = start,
Subject = subject,
Type = type
Ciphertext = descriptor.Ciphertext,
CreationDate = descriptor.CreationDate,
ExpirationDate = descriptor.ExpirationDate,
Hash = descriptor.Hash,
Subject = descriptor.Subject,
Type = descriptor.Type
};
return CreateAsync(token, cancellationToken);
}
// Bind the token to the specified client application.
var key = ConvertIdentifierFromString(descriptor.ApplicationId);
/// <summary>
/// Creates a new reference token, which is associated with a particular subject.
/// </summary>
/// <param name="type">The token type.</param>
/// <param name="subject">The subject associated with the token.</param>
/// <param name="hash">The hash of the crypto-secure random identifier associated with the token.</param>
/// <param name="ciphertext">The ciphertext associated with the token.</param>
/// <param name="start">The date on which the token will start to be considered valid.</param>
/// <param name="end">The date on which the token will no longer be considered valid.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the token.
/// </returns>
public virtual Task<TToken> CreateAsync(
[NotNull] string type, [NotNull] string subject, [NotNull] string hash, [NotNull] string ciphertext,
[CanBeNull] DateTimeOffset? start, [CanBeNull] DateTimeOffset? end, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(type))
var application = await Applications.SingleOrDefaultAsync(entity => entity.Id.Equals(key));
if (application == null)
{
throw new ArgumentException("The token type cannot be null or empty.");
throw new InvalidOperationException("The application associated with the token cannot be found.");
}
if (string.IsNullOrEmpty(subject))
token.Application = application;
// Bind the token to the specified authorization.
key = ConvertIdentifierFromString(descriptor.AuthorizationId);
var authorization = await Authorizations.SingleOrDefaultAsync(entity => entity.Id.Equals(key));
if (authorization == null)
{
throw new ArgumentException("The subject cannot be null or empty.");
throw new InvalidOperationException("The authorization associated with the token cannot be found.");
}
var token = new TToken
{
Ciphertext = ciphertext,
End = end,
Hash = hash,
Start = start,
Subject = subject,
Type = type
};
token.Authorization = authorization;
return CreateAsync(token, cancellationToken);
return await CreateAsync(token, cancellationToken);
}
/// <summary>
@ -316,6 +289,44 @@ namespace OpenIddict.EntityFrameworkCore
return Task.FromResult(token.Ciphertext);
}
/// <summary>
/// Retrieves the creation date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the creation date associated with the specified token.
/// </returns>
public virtual Task<DateTimeOffset?> GetCreationDateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return Task.FromResult(token.CreationDate);
}
/// <summary>
/// Retrieves the expiration date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
/// whose result returns the expiration date associated with the specified token.
/// </returns>
public virtual Task<DateTimeOffset?> GetExpirationDateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return Task.FromResult(token.ExpirationDate);
}
/// <summary>
/// Retrieves the hashed identifier associated with a token.
/// </summary>
@ -497,6 +508,23 @@ namespace OpenIddict.EntityFrameworkCore
}
}
/// <summary>
/// Sets the expiration date associated with a token.
/// </summary>
/// <param name="token">The token.</param>
/// <param name="date">The date on which the token will no longer be considered valid.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task SetExpirationDateAsync([NotNull] TToken token,
[CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken)
{
token.ExpirationDate = date;
return Task.CompletedTask;
}
/// <summary>
/// Sets the status associated with a token.
/// </summary>

14
src/OpenIddict.Models/OpenIddictToken.cs

@ -50,11 +50,17 @@ namespace OpenIddict.Models
/// </summary>
public virtual string Ciphertext { get; set; }
/// <summary>
/// Gets or sets the date on which the token
/// will start to be considered valid.
/// </summary>
public virtual DateTimeOffset? CreationDate { get; set; }
/// <summary>
/// Gets or sets the date on which the token
/// will no longer be considered valid.
/// </summary>
public virtual DateTimeOffset? End { get; set; }
public virtual DateTimeOffset? ExpirationDate { get; set; }
/// <summary>
/// Gets or sets the hashed identifier associated
@ -69,12 +75,6 @@ namespace OpenIddict.Models
/// </summary>
public virtual TKey Id { get; set; }
/// <summary>
/// Gets or sets the date on which the token
/// will start to be considered valid.
/// </summary>
public virtual DateTimeOffset? Start { get; set; }
/// <summary>
/// Gets or sets the status of the current token.
/// </summary>

18
src/OpenIddict/OpenIddictExtensions.cs

@ -940,5 +940,23 @@ namespace Microsoft.AspNetCore.Builder
return builder.Configure(options => options.UseReferenceTokens = true);
}
/// <summary>
/// Configures OpenIddict to use rolling refresh tokens. When this option is enabled,
/// a new refresh token is issued for each refresh token request and the previous one
/// is automatically revoked (when disabled, no new refresh token is issued and the
/// lifetime of the original refresh token is increased by updating the database entry).
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
public static OpenIddictBuilder UseRollingTokens([NotNull] this OpenIddictBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
return builder.Configure(options => options.UseRollingTokens = true);
}
}
}

10
src/OpenIddict/OpenIddictInitializer.cs

@ -158,6 +158,16 @@ namespace OpenIddict
"Reference tokens cannot be used when configuring JWT as the access token format.");
}
if (options.UseRollingTokens && options.DisableTokenRevocation)
{
throw new InvalidOperationException("Rolling tokens cannot be used when disabling token expiration.");
}
if (options.UseRollingTokens && !options.UseSlidingExpiration)
{
throw new InvalidOperationException("Rolling tokens cannot be used without enabling sliding expiration.");
}
if (options.AccessTokenHandler != null && options.SigningCredentials.Count == 0)
{
throw new InvalidOperationException(

9
src/OpenIddict/OpenIddictOptions.cs

@ -87,5 +87,14 @@ namespace OpenIddict
/// Note: this option cannot be used when configuring JWT as the access token format.
/// </summary>
public bool UseReferenceTokens { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether rolling tokens should be used.
/// When disabled, no new token is issued and the refresh token lifetime is
/// dynamically managed by updating the token entry in the database.
/// When this option is enabled, a new refresh token is issued for each
/// refresh token request and the previous one is automatically revoked.
/// </summary>
public bool UseRollingTokens { get; set; }
}
}

33
src/OpenIddict/OpenIddictProvider.Exchange.cs

@ -214,6 +214,9 @@ namespace OpenIddict
var identifier = context.Ticket.GetProperty(OpenIdConnectConstants.Properties.TokenId);
Debug.Assert(!string.IsNullOrEmpty(identifier), "The authentication ticket should contain a ticket identifier.");
// Store the original authorization code/refresh token so it can be later retrieved.
context.Request.SetProperty(OpenIddictConstants.Properties.TokenId, identifier);
if (context.Request.IsAuthorizationCodeGrantType())
{
// Retrieve the authorization code from the database and ensure it is still valid.
@ -259,7 +262,7 @@ namespace OpenIddict
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The specified authorization code has already been redemeed.");
description: "The specified authorization code has already been redeemed.");
return;
}
@ -283,7 +286,29 @@ namespace OpenIddict
{
// Retrieve the token from the database and ensure it is still valid.
var token = await Tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
if (token == null || !await Tokens.IsValidAsync(token, context.HttpContext.RequestAborted))
if (token == null)
{
Logger.LogError("The token request was rejected because the refresh token was already redeemed.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The specified refresh token is no longer valid.");
return;
}
else if (await Tokens.IsRedeemedAsync(token, context.HttpContext.RequestAborted))
{
Logger.LogError("The token request was rejected because the refresh token was no longer valid.");
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The specified refresh token has already been redeemed.");
return;
}
else if (!await Tokens.IsValidAsync(token, context.HttpContext.RequestAborted))
{
Logger.LogError("The token request was rejected because the refresh token was no longer valid.");
@ -294,10 +319,10 @@ namespace OpenIddict
return;
}
// When sliding expiration is enabled, immediately
// When rolling tokens are enabled, immediately
// redeem the refresh token to prevent future reuse.
// See https://tools.ietf.org/html/rfc6749#section-6.
if (options.UseSlidingExpiration)
if (options.UseRollingTokens)
{
await Tokens.RedeemAsync(token, context.HttpContext.RequestAborted);
}

161
src/OpenIddict/OpenIddictProvider.Serialization.cs

@ -7,7 +7,6 @@
using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
@ -117,6 +116,8 @@ namespace OpenIddict
public override async Task SerializeAuthorizationCode([NotNull] SerializeAuthorizationCodeContext context)
{
Debug.Assert(context.Request.IsAuthorizationRequest(), "The request should be an authorization request.");
var token = await CreateTokenAsync(
OpenIdConnectConstants.TokenUsages.AuthorizationCode,
context.Ticket, (OpenIddictOptions) context.Options,
@ -136,9 +137,36 @@ namespace OpenIddict
public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context)
{
var options = (OpenIddictOptions) context.Options;
Debug.Assert(context.Request.IsTokenRequest(), "The request should be a token request.");
// When rolling tokens are disabled, extend the expiration date associated with the
// existing token instead of returning a new refresh token with a new expiration date.
if (options.UseSlidingExpiration && !options.UseRollingTokens && context.Request.IsRefreshTokenGrantType())
{
var identifier = context.Request.GetProperty<string>(OpenIddictConstants.Properties.TokenId);
var entry = await Tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
if (entry != null)
{
Logger.LogInformation("The expiration date of the '{Identifier}' token was automatically updated: {Date}.",
identifier, context.Ticket.Properties.ExpiresUtc);
await Tokens.ExtendAsync(entry, context.Ticket.Properties.ExpiresUtc, context.HttpContext.RequestAborted);
context.RefreshToken = null;
context.HandleSerialization();
return;
}
// If the refresh token entry could not be
// found in the database, generate a new one.
}
var token = await CreateTokenAsync(
OpenIdConnectConstants.TokenUsages.RefreshToken,
context.Ticket, (OpenIddictOptions) context.Options,
OpenIdConnectConstants.TokenUsages.RefreshToken, context.Ticket, options,
context.HttpContext, context.Request, context.DataFormat);
// If a reference token was returned by CreateTokenAsync(),
@ -162,7 +190,10 @@ namespace OpenIddict
Debug.Assert(!(options.DisableTokenRevocation && options.UseReferenceTokens),
"Token revocation cannot be disabled when using reference tokens.");
Debug.Assert(!string.Equals(type, OpenIdConnectConstants.TokenUsages.IdToken, StringComparison.OrdinalIgnoreCase),
Debug.Assert(!(options.DisableTokenRevocation && options.UseRollingTokens),
"Token revocation cannot be disabled when using rolling tokens.");
Debug.Assert(type != OpenIdConnectConstants.TokenUsages.IdToken,
"Identity tokens shouldn't be stored in the database.");
if (options.DisableTokenRevocation)
@ -170,28 +201,34 @@ namespace OpenIddict
return null;
}
// Resolve the subject from the authentication ticket. If it cannot be found, throw an exception.
var subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject);
if (string.IsNullOrEmpty(subject))
var descriptor = new OpenIddictTokenDescriptor
{
throw new InvalidOperationException("The subject associated with the authentication ticket cannot be retrieved.");
}
CreationDate = ticket.Properties.IssuedUtc,
ExpirationDate = ticket.Properties.ExpiresUtc,
Subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject),
Type = type
};
TToken token;
string result = null;
// When reference tokens are enabled or when the token is an authorization code or a
// refresh token, remove the unnecessary properties from the authentication ticket.
if (options.UseReferenceTokens ||
(type == OpenIdConnectConstants.TokenUsages.AuthorizationCode ||
type == OpenIdConnectConstants.TokenUsages.RefreshToken))
{
ticket.Properties.IssuedUtc = ticket.Properties.ExpiresUtc = null;
ticket.RemoveProperty(OpenIdConnectConstants.Properties.TokenId);
}
// If reference tokens are enabled, create a new entry for
// authorization codes, refresh tokens and access tokens.
if (options.UseReferenceTokens)
{
// When the token is a reference token, remove the token identifier from the
// authentication ticket as it is restored when receiving and decrypting it.
ticket.RemoveProperty(OpenIdConnectConstants.Properties.TokenId);
// Note: the data format is automatically replaced at startup time to ensure
// that encrypted tokens stored in the database cannot be considered as
// valid tokens if the developer decides to disable reference tokens support.
var ciphertext = format.Protect(ticket);
descriptor.Ciphertext = format.Protect(ticket);
// Generate a new crypto-secure random identifier that will be
// substituted to the ciphertext returned by the data format.
@ -203,49 +240,19 @@ namespace OpenIddict
// it as the hashed identifier of the reference token.
// Doing that prevents token identifiers stolen from
// the database from being used as valid reference tokens.
string hash;
using (var algorithm = SHA256.Create())
{
hash = Convert.ToBase64String(algorithm.ComputeHash(bytes));
descriptor.Hash = Convert.ToBase64String(algorithm.ComputeHash(bytes));
}
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,
ticket.Properties.IssuedUtc,
ticket.Properties.ExpiresUtc, context.RequestAborted);
}
else
{
return null;
}
// If a null value was returned by CreateAsync(), return immediately.
if (token == null)
else if (type != OpenIdConnectConstants.TokenUsages.AuthorizationCode &&
type != OpenIdConnectConstants.TokenUsages.RefreshToken)
{
return null;
}
// Throw an exception if the token identifier can't be resolved.
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.");
}
// Attach the key returned by the underlying store
// to the refresh token to override the default GUID
// generated by the OpenID Connect server middleware.
ticket.SetTokenId(identifier);
// If the client application is known, associate it with the token.
if (!string.IsNullOrEmpty(request.ClientId))
{
@ -255,41 +262,61 @@ namespace OpenIddict
throw new InvalidOperationException("The client application cannot be retrieved from the database.");
}
var key = await Applications.GetIdAsync(application, context.RequestAborted);
await Tokens.SetClientAsync(token, key, context.RequestAborted);
descriptor.ApplicationId = await Applications.GetIdAsync(application, 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), context.RequestAborted);
descriptor.AuthorizationId = ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId);
}
// Otherwise, create an ad-hoc authorization if the token is an authorization code.
else if (string.Equals(type, OpenIdConnectConstants.TokenUsages.AuthorizationCode, StringComparison.OrdinalIgnoreCase))
else if (type == OpenIdConnectConstants.TokenUsages.AuthorizationCode)
{
Debug.Assert(!string.IsNullOrEmpty(request.ClientId), "The client identifier shouldn't be null.");
Debug.Assert(!string.IsNullOrEmpty(descriptor.ApplicationId), "The client identifier shouldn't be null.");
var application = await Applications.FindByClientIdAsync(request.ClientId, context.RequestAborted);
if (application == null)
var authorization = await Authorizations.CreateAsync(new OpenIddictAuthorizationDescriptor
{
throw new InvalidOperationException("The client application cannot be retrieved from the database.");
}
var authorization = await Authorizations.CreateAsync(subject,
await Applications.GetIdAsync(application, context.RequestAborted), request.GetScopes(), context.RequestAborted);
ApplicationId = descriptor.ApplicationId,
Scopes = request.GetScopes(),
Subject = descriptor.Subject
}, context.RequestAborted);
if (authorization != null)
{
var key = await Authorizations.GetIdAsync(authorization, context.RequestAborted);
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, key);
descriptor.AuthorizationId = await Authorizations.GetIdAsync(authorization, context.RequestAborted);
await Tokens.SetAuthorizationAsync(token, key, context.RequestAborted);
Logger.LogInformation("An ad-hoc authorization was automatically created and " +
"associated with the '{ClientId}' application: {Identifier}.",
request.ClientId, descriptor.AuthorizationId);
}
}
// If a null value was returned by CreateAsync(), return immediately.
var token = await Tokens.CreateAsync(descriptor, context.RequestAborted);
if (token == null)
{
return null;
}
// Throw an exception if the token identifier can't be resolved.
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.");
}
// Restore the token identifier using the unique
// identifier attached with the database entry.
ticket.SetTokenId(identifier);
// Dynamically set the creation and expiration dates.
ticket.Properties.IssuedUtc = await Tokens.GetCreationDateAsync(token, context.RequestAborted);
ticket.Properties.ExpiresUtc = await Tokens.GetExpirationDateAsync(token, context.RequestAborted);
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, descriptor.AuthorizationId);
if (!string.IsNullOrEmpty(result))
{
Logger.LogTrace("A new reference token was successfully generated and persisted " +
@ -373,6 +400,10 @@ namespace OpenIddict
// identifier attached with the database entry.
ticket.SetTokenId(identifier);
// Dynamically set the creation and expiration dates.
ticket.Properties.IssuedUtc = await Tokens.GetCreationDateAsync(token, context.RequestAborted);
ticket.Properties.ExpiresUtc = await Tokens.GetExpirationDateAsync(token, context.RequestAborted);
// If the authorization identifier cannot be found in the ticket properties,
// try to restore it using the identifier associated with the database entry.
if (!ticket.HasProperty(OpenIddictConstants.Properties.AuthorizationId))

46
test/OpenIddict.Tests/OpenIddictInitializerTests.cs

@ -155,6 +155,52 @@ namespace OpenIddict.Tests
Assert.Equal("Reference tokens cannot be used when disabling token revocation.", exception.Message);
}
[Fact]
public async Task PostConfigure_ThrowsAnExceptionWhenUsingRollingTokensWithTokenRevocationDisabled()
{
// Arrange
var server = CreateAuthorizationServer(builder =>
{
builder.EnableAuthorizationEndpoint("/connect/authorize")
.AllowImplicitFlow()
.DisableTokenRevocation()
.UseRollingTokens();
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act and assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(delegate
{
return client.GetAsync("/");
});
Assert.Equal("Rolling tokens cannot be used when disabling token expiration.", exception.Message);
}
[Fact]
public async Task PostConfigure_ThrowsAnExceptionWhenUsingRollingTokensWithSlidingExpirationDisabled()
{
// Arrange
var server = CreateAuthorizationServer(builder =>
{
builder.EnableAuthorizationEndpoint("/connect/authorize")
.AllowImplicitFlow()
.UseRollingTokens()
.Configure(options => options.UseSlidingExpiration = false);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act and assert
var exception = await Assert.ThrowsAsync<InvalidOperationException>(delegate
{
return client.GetAsync("/");
});
Assert.Equal("Rolling tokens cannot be used without enabling sliding expiration.", exception.Message);
}
[Fact]
public async Task PostConfigure_ThrowsAnExceptionWhenUsingReferenceTokensIfAnAccessTokenHandlerIsSet()
{

8
test/OpenIddict.Tests/OpenIddictProviderTests.Authentication.cs

@ -554,16 +554,16 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
instance.Setup(mock => mock.GetIdAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
}));
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
var token = new OpenIddictToken();
instance.Setup(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
It.IsAny<DateTimeOffset?>(), It.IsAny<DateTimeOffset?>(),
It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))

74
test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs

@ -577,7 +577,7 @@ namespace OpenIddict.Tests
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The specified authorization code has already been redemeed.", response.ErrorDescription);
Assert.Equal("The specified authorization code has already been redeemed.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()), Times.Once());
@ -660,7 +660,7 @@ namespace OpenIddict.Tests
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The specified authorization code has already been redemeed.", response.ErrorDescription);
Assert.Equal("The specified authorization code has already been redeemed.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.IsRedeemedAsync(tokens[0], It.IsAny<CancellationToken>()), Times.Once());
@ -796,6 +796,69 @@ namespace OpenIddict.Tests
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsAlreadyRedeemed()
{
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(CreateApplicationManager(instance =>
{
var application = new OpenIddictApplication();
instance.Setup(mock => mock.FindByClientIdAsync("Fabrikam", It.IsAny<CancellationToken>()))
.ReturnsAsync(application);
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.RefreshTokenFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest
{
GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken,
RefreshToken = "8xLOxBtZp8"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The specified refresh token has already been redeemed.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsInvalid()
{
@ -820,6 +883,9 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
});
@ -926,7 +992,7 @@ namespace OpenIddict.Tests
}
[Fact]
public async Task HandleTokenRequest_RefreshTokenIsAutomaticallyRedeemedWhenSlidingExpirationIsEnabled()
public async Task HandleTokenRequest_RefreshTokenIsAutomaticallyRedeemedWhenRollingTokensAreEnabled()
{
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
@ -974,6 +1040,8 @@ namespace OpenIddict.Tests
builder.Services.AddSingleton(manager);
builder.UseRollingTokens();
builder.Configure(options => options.RefreshTokenFormat = format.Object);
});

3
test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs

@ -650,6 +650,9 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Confidential);
instance.Setup(mock => mock.GetIdAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
instance.Setup(mock => mock.ValidateClientSecretAsync(application, "7Fjfp0ZBr1KtDRbnfVdmIw", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));

298
test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs

@ -46,8 +46,9 @@ namespace OpenIddict.Tests
Assert.NotNull(response.AccessToken);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique",
It.IsAny<DateTimeOffset?>(), It.IsAny<DateTimeOffset?>(),
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.Subject == "Bob le Magnifique" &&
descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AccessToken),
It.IsAny<CancellationToken>()), Times.Never());
}
@ -57,16 +58,13 @@ namespace OpenIddict.Tests
// Arrange
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),
CreationDate = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero),
ExpirationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero)
};
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique",
It.IsNotNull<string>(), It.IsNotNull<string>(),
token.Start, token.End, It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
@ -81,8 +79,8 @@ namespace OpenIddict.Tests
builder.Configure(options =>
{
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == token.Start.Value);
options.AccessTokenLifetime = token.End.Value - token.Start.Value;
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == token.CreationDate.Value);
options.AccessTokenLifetime = token.ExpirationDate.Value - token.CreationDate.Value;
});
});
@ -101,10 +99,14 @@ namespace OpenIddict.Tests
Assert.NotNull(response.AccessToken);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique",
It.IsNotNull<string>(), It.IsNotNull<string>(),
token.Start, token.End, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()), Times.Once());
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.Ciphertext != null &&
descriptor.Hash != null &&
descriptor.ExpirationDate == token.ExpirationDate &&
descriptor.CreationDate == token.CreationDate &&
descriptor.Subject == "Bob le Magnifique" &&
descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AccessToken),
It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -115,18 +117,11 @@ namespace OpenIddict.Tests
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique",
It.IsNotNull<string>(), It.IsNotNull<string>(),
It.IsAny<DateTimeOffset?>(), It.IsAny<DateTimeOffset?>(),
It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
instance.Setup(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder =>
@ -165,7 +160,12 @@ namespace OpenIddict.Tests
// Assert
Assert.NotNull(response.AccessToken);
Mock.Get(manager).Verify(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.CreateAsync(
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" &&
descriptor.Subject == "Bob le Magnifique" &&
descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AccessToken),
It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -176,18 +176,11 @@ namespace OpenIddict.Tests
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.AccessToken, "Bob le Magnifique",
It.IsNotNull<string>(), It.IsNotNull<string>(),
It.IsAny<DateTimeOffset?>(), It.IsAny<DateTimeOffset?>(),
It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
instance.Setup(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder =>
@ -218,7 +211,12 @@ namespace OpenIddict.Tests
// Assert
Assert.NotNull(response.AccessToken);
Mock.Get(manager).Verify(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.CreateAsync(
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.AuthorizationId == "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70" &&
descriptor.Subject == "Bob le Magnifique" &&
descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AccessToken),
It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -267,8 +265,7 @@ namespace OpenIddict.Tests
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
It.IsAny<DateTimeOffset?>(), It.IsAny<DateTimeOffset?>(),
It.IsAny<OpenIddictTokenDescriptor>(),
It.IsAny<CancellationToken>()), Times.Never());
}
@ -278,15 +275,13 @@ namespace OpenIddict.Tests
// Arrange
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),
CreationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
ExpirationDate = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero)
};
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
token.Start, token.End, It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
@ -310,14 +305,17 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
instance.Setup(mock => mock.GetIdAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
}));
builder.Services.AddSingleton(manager);
builder.Configure(options =>
{
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == token.Start.Value);
options.AuthorizationCodeLifetime = token.End.Value - token.Start.Value;
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == token.CreationDate.Value);
options.AuthorizationCodeLifetime = token.ExpirationDate.Value - token.CreationDate.Value;
});
});
@ -335,9 +333,14 @@ namespace OpenIddict.Tests
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
token.Start, token.End, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()), Times.Once());
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.Ciphertext == null &&
descriptor.Hash == null &&
descriptor.ExpirationDate == token.ExpirationDate &&
descriptor.CreationDate == token.CreationDate &&
descriptor.Subject == "Bob le Magnifique" &&
descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AuthorizationCode),
It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -346,16 +349,13 @@ namespace OpenIddict.Tests
// Arrange
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),
CreationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
ExpirationDate = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero)
};
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
It.IsNotNull<string>(), It.IsNotNull<string>(),
token.Start, token.End, It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
@ -379,6 +379,9 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.GetClientTypeAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
instance.Setup(mock => mock.GetIdAsync(application, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
}));
builder.Services.AddSingleton(manager);
@ -387,8 +390,8 @@ namespace OpenIddict.Tests
builder.Configure(options =>
{
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == token.Start.Value);
options.AuthorizationCodeLifetime = token.End.Value - token.Start.Value;
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == token.CreationDate.Value);
options.AuthorizationCodeLifetime = token.ExpirationDate.Value - token.CreationDate.Value;
});
});
@ -406,10 +409,14 @@ namespace OpenIddict.Tests
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
It.IsNotNull<string>(), It.IsNotNull<string>(),
token.Start, token.End, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()), Times.Once());
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.Ciphertext != null &&
descriptor.Hash != null &&
descriptor.ExpirationDate == token.ExpirationDate &&
descriptor.CreationDate == token.CreationDate &&
descriptor.Subject == "Bob le Magnifique" &&
descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AuthorizationCode),
It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -420,17 +427,11 @@ namespace OpenIddict.Tests
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
It.IsAny<DateTimeOffset?>(), It.IsAny<DateTimeOffset?>(),
It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
instance.Setup(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder =>
@ -471,7 +472,12 @@ namespace OpenIddict.Tests
// Assert
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.CreateAsync(
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" &&
descriptor.Subject == "Bob le Magnifique" &&
descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AuthorizationCode),
It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -482,20 +488,11 @@ namespace OpenIddict.Tests
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
It.IsAny<DateTimeOffset?>(), It.IsAny<DateTimeOffset?>(),
It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
instance.Setup(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(0));
instance.Setup(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder =>
@ -543,7 +540,13 @@ namespace OpenIddict.Tests
// Assert
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.CreateAsync(
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" &&
descriptor.AuthorizationId == "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70" &&
descriptor.Subject == "Bob le Magnifique" &&
descriptor.Type == OpenIdConnectConstants.TokenTypeHints.AuthorizationCode),
It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -582,11 +585,8 @@ namespace OpenIddict.Tests
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
instance.Setup(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.AuthorizationCode, "Bob le Magnifique",
It.IsAny<DateTimeOffset?>(), It.IsAny<DateTimeOffset?>(),
It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
@ -608,8 +608,61 @@ namespace OpenIddict.Tests
// Assert
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.CreateAsync("Bob le Magnifique", "3E228451-1555-46F7-A471-951EFBA23A56",
It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.CreateAsync(
It.Is<OpenIddictAuthorizationDescriptor>(descriptor =>
descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" &&
descriptor.Subject == "Bob le Magnifique"),
It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task SerializeRefreshToken_ExtendsLifetimeWhenRollingTokensAreDisabled()
{
// Arrange
var token = new OpenIddictToken
{
CreationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
ExpirationDate = new DateTimeOffset(2017, 01, 10, 00, 00, 00, TimeSpan.Zero)
};
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.FindByHashAsync("d80c119138b3aaeefce94093032c0204c547dc27cc5fe97f32933becd48b7bf5", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(manager);
builder.UseReferenceTokens();
builder.Configure(options =>
{
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow ==
new DateTimeOffset(2017, 01, 05, 00, 00, 00, TimeSpan.Zero));
options.RefreshTokenLifetime = TimeSpan.FromDays(10);
});
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest
{
GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken,
RefreshToken = "3E228451-1555-46F7-A471-951EFBA23A56"
});
// Assert
Assert.Null(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.ExtendAsync(token,
new DateTimeOffset(2017, 01, 15, 00, 00, 00, TimeSpan.Zero),
It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
@ -642,8 +695,7 @@ namespace OpenIddict.Tests
Assert.NotNull(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique",
It.IsAny<DateTimeOffset?>(), It.IsAny<DateTimeOffset?>(),
It.IsAny<OpenIddictTokenDescriptor>(),
It.IsAny<CancellationToken>()), Times.Never());
}
@ -653,15 +705,13 @@ namespace OpenIddict.Tests
// Arrange
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)
CreationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
ExpirationDate = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero)
};
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique",
token.Start, token.End, It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
@ -674,8 +724,8 @@ namespace OpenIddict.Tests
builder.Configure(options =>
{
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == token.Start.Value);
options.RefreshTokenLifetime = token.End.Value - token.Start.Value;
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == token.CreationDate.Value);
options.RefreshTokenLifetime = token.ExpirationDate.Value - token.CreationDate.Value;
});
});
@ -694,9 +744,14 @@ namespace OpenIddict.Tests
Assert.NotNull(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique",
token.Start, token.End, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()), Times.Once());
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.Ciphertext == null &&
descriptor.Hash == null &&
descriptor.ExpirationDate == token.ExpirationDate &&
descriptor.CreationDate == token.CreationDate &&
descriptor.Subject == "Bob le Magnifique" &&
descriptor.Type == OpenIdConnectConstants.TokenTypeHints.RefreshToken),
It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -705,16 +760,13 @@ namespace OpenIddict.Tests
// Arrange
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),
CreationDate = new DateTimeOffset(2017, 01, 01, 00, 00, 00, TimeSpan.Zero),
ExpirationDate = new DateTimeOffset(2017, 01, 02, 00, 00, 00, TimeSpan.Zero)
};
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique",
It.IsNotNull<string>(), It.IsNotNull<string>(),
token.Start, token.End, It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
@ -729,8 +781,8 @@ namespace OpenIddict.Tests
builder.Configure(options =>
{
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == token.Start.Value);
options.RefreshTokenLifetime = token.End.Value - token.Start.Value;
options.SystemClock = Mock.Of<ISystemClock>(mock => mock.UtcNow == token.CreationDate.Value);
options.RefreshTokenLifetime = token.ExpirationDate.Value - token.CreationDate.Value;
});
});
@ -749,10 +801,14 @@ namespace OpenIddict.Tests
Assert.NotNull(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique",
It.IsNotNull<string>(), It.IsNotNull<string>(),
token.Start, token.End, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()), Times.Once());
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.Ciphertext != null &&
descriptor.Hash != null &&
descriptor.ExpirationDate == token.ExpirationDate &&
descriptor.CreationDate == token.CreationDate &&
descriptor.Subject == "Bob le Magnifique" &&
descriptor.Type == OpenIdConnectConstants.TokenTypeHints.RefreshToken),
It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -763,17 +819,11 @@ namespace OpenIddict.Tests
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique",
It.IsAny<DateTimeOffset?>(), It.IsAny<DateTimeOffset?>(),
It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
instance.Setup(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder =>
@ -810,7 +860,12 @@ namespace OpenIddict.Tests
// Assert
Assert.NotNull(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.SetClientAsync(token, "3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.CreateAsync(
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" &&
descriptor.Subject == "Bob le Magnifique" &&
descriptor.Type == OpenIdConnectConstants.TokenTypeHints.RefreshToken),
It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
@ -821,17 +876,11 @@ namespace OpenIddict.Tests
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.CreateAsync(
OpenIdConnectConstants.TokenTypeHints.RefreshToken, "Bob le Magnifique",
It.IsAny<DateTimeOffset?>(), It.IsAny<DateTimeOffset?>(),
It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.CreateAsync(It.IsAny<OpenIddictTokenDescriptor>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.GetIdAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync("3E228451-1555-46F7-A471-951EFBA23A56");
instance.Setup(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>()))
.Returns(Task.FromResult(0));
});
var server = CreateAuthorizationServer(builder =>
@ -860,7 +909,12 @@ namespace OpenIddict.Tests
// Assert
Assert.NotNull(response.RefreshToken);
Mock.Get(manager).Verify(mock => mock.SetAuthorizationAsync(token, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.CreateAsync(
It.Is<OpenIddictTokenDescriptor>(descriptor =>
descriptor.AuthorizationId == "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70" &&
descriptor.Subject == "Bob le Magnifique" &&
descriptor.Type == OpenIdConnectConstants.TokenTypeHints.RefreshToken),
It.IsAny<CancellationToken>()), Times.Once());
}
}
}

Loading…
Cancel
Save