Browse Source

Backport the automatic revocation changes to OpenIddict 1.x

pull/553/head
Kévin Chalet 9 years ago
parent
commit
2205e53dc4
  1. 2
      build/dependencies.props
  2. 5
      src/OpenIddict.Core/Descriptors/OpenIddictAuthorizationDescriptor.cs
  3. 90
      src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
  4. 8
      src/OpenIddict.Core/OpenIddictConstants.cs
  5. 22
      src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs
  6. 45
      src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs
  7. 10
      src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs
  8. 75
      src/OpenIddict.EntityFramework/OpenIddictExtensions.cs
  9. 3
      src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
  10. 24
      src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs
  11. 3
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
  12. 5
      src/OpenIddict.Models/OpenIddictAuthorization.cs
  13. 20
      src/OpenIddict/OpenIddictExtensions.cs
  14. 3
      src/OpenIddict/OpenIddictOptions.cs
  15. 154
      src/OpenIddict/OpenIddictProvider.Exchange.cs
  16. 365
      src/OpenIddict/OpenIddictProvider.Helpers.cs
  17. 303
      src/OpenIddict/OpenIddictProvider.Serialization.cs
  18. 146
      src/OpenIddict/OpenIddictProvider.Signin.cs
  19. 29
      test/OpenIddict.Tests/OpenIddictExtensionsTests.cs
  20. 300
      test/OpenIddict.Tests/OpenIddictProviderTests.Exchange.cs
  21. 2
      test/OpenIddict.Tests/OpenIddictProviderTests.Introspection.cs
  22. 1290
      test/OpenIddict.Tests/OpenIddictProviderTests.Serialization.cs
  23. 723
      test/OpenIddict.Tests/OpenIddictProviderTests.Signin.cs
  24. 28
      test/OpenIddict.Tests/OpenIddictProviderTests.cs

2
build/dependencies.props

@ -3,7 +3,7 @@
<PropertyGroup>
<AspNetCoreVersion>1.0.0</AspNetCoreVersion>
<AspNetContribOpenIdExtensionsVersion>1.0.0</AspNetContribOpenIdExtensionsVersion>
<AspNetContribOpenIdServerVersion>1.0.1</AspNetContribOpenIdServerVersion>
<AspNetContribOpenIdServerVersion>1.0.2</AspNetContribOpenIdServerVersion>
<CryptoHelperVersion>2.0.0</CryptoHelperVersion>
<EntityFrameworkVersion>6.1.3</EntityFrameworkVersion>
<JetBrainsVersion>10.3.0</JetBrainsVersion>

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

@ -28,5 +28,10 @@ namespace OpenIddict.Core
/// Gets or sets the subject associated with the authorization.
/// </summary>
public string Subject { get; set; }
/// <summary>
/// Gets or sets the type of the authorization.
/// </summary>
public virtual string Type { get; set; }
}
}

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

@ -85,6 +85,12 @@ namespace OpenIddict.Core
throw new ArgumentNullException(nameof(authorization));
}
// If no type was explicitly specified, assume that the authorization is a permanent authorization.
if (string.IsNullOrEmpty(await Store.GetTypeAsync(authorization, cancellationToken)))
{
await Store.SetTypeAsync(authorization, OpenIddictConstants.AuthorizationTypes.Permanent, cancellationToken);
}
await ValidateAsync(authorization, cancellationToken);
return await Store.CreateAsync(authorization, cancellationToken);
}
@ -104,6 +110,13 @@ namespace OpenIddict.Core
throw new ArgumentNullException(nameof(descriptor));
}
// If no type was explicitly specified, assume that
// the authorization is a permanent authorization.
if (string.IsNullOrEmpty(descriptor.Type))
{
descriptor.Type = OpenIddictConstants.AuthorizationTypes.Permanent;
}
await ValidateAsync(descriptor, cancellationToken);
return await Store.CreateAsync(descriptor, cancellationToken);
}
@ -194,6 +207,69 @@ namespace OpenIddict.Core
return Store.GetIdAsync(authorization, cancellationToken);
}
/// <summary>
/// Retrieves the status associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</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 status associated with the specified authorization.
/// </returns>
public virtual Task<string> GetStatusAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return Store.GetStatusAsync(authorization, cancellationToken);
}
/// <summary>
/// Determines whether a given authorization has been revoked.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns><c>true</c> if the authorization has been revoked, <c>false</c> otherwise.</returns>
public virtual async Task<bool> IsRevokedAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
var status = await Store.GetStatusAsync(authorization, cancellationToken);
if (string.IsNullOrEmpty(status))
{
return false;
}
return string.Equals(status, OpenIddictConstants.Statuses.Revoked, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Determines whether a given authorization is valid.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
/// <returns><c>true</c> if the authorization is valid, <c>false</c> otherwise.</returns>
public virtual async Task<bool> IsValidAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
var status = await Store.GetStatusAsync(authorization, cancellationToken);
if (string.IsNullOrEmpty(status))
{
return false;
}
return string.Equals(status, OpenIddictConstants.Statuses.Valid, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Executes the specified query.
/// </summary>
@ -288,7 +364,8 @@ namespace OpenIddict.Core
var descriptor = new OpenIddictAuthorizationDescriptor
{
Status = await Store.GetStatusAsync(authorization, cancellationToken),
Subject = await Store.GetSubjectAsync(authorization, cancellationToken)
Subject = await Store.GetSubjectAsync(authorization, cancellationToken),
Type = await Store.GetTypeAsync(authorization, cancellationToken)
};
await ValidateAsync(descriptor, cancellationToken);
@ -309,6 +386,17 @@ namespace OpenIddict.Core
throw new ArgumentNullException(nameof(descriptor));
}
if (string.IsNullOrEmpty(descriptor.Type))
{
throw new ArgumentException("The authorization type cannot be null or empty.", nameof(descriptor));
}
if (!string.Equals(descriptor.Type, OpenIddictConstants.AuthorizationTypes.AdHoc, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(descriptor.Type, OpenIddictConstants.AuthorizationTypes.Permanent, StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("The specified authorization type is not supported by the default token manager.");
}
if (string.IsNullOrEmpty(descriptor.Status))
{
throw new ArgumentException("The status cannot be null or empty.");

8
src/OpenIddict.Core/OpenIddictConstants.cs

@ -8,6 +8,12 @@ namespace OpenIddict.Core
{
public static class OpenIddictConstants
{
public static class AuthorizationTypes
{
public const string AdHoc = "ad-hoc";
public const string Permanent = "permanent";
}
public static class Claims
{
public const string Roles = "roles";
@ -33,8 +39,8 @@ namespace OpenIddict.Core
public static class Properties
{
public const string AuthenticationTicket = ".authentication_ticket";
public const string AuthorizationId = ".authorization_id";
public const string TokenId = ".token_id";
}
public static class Separators

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

@ -138,6 +138,17 @@ namespace OpenIddict.Core
/// </returns>
Task<string> GetSubjectAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken);
/// <summary>
/// Retrieves the type associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</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 type associated with the specified authorization.
/// </returns>
Task<string> GetTypeAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken);
/// <summary>
/// Executes the specified query.
/// </summary>
@ -173,6 +184,17 @@ namespace OpenIddict.Core
/// </returns>
Task SetStatusAsync([NotNull] TAuthorization authorization, [NotNull] string status, CancellationToken cancellationToken);
/// <summary>
/// Sets the type associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="type">The type associated with the authorization.</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 SetTypeAsync([NotNull] TAuthorization authorization, [NotNull] string type, CancellationToken cancellationToken);
/// <summary>
/// Updates an existing authorization.
/// </summary>

45
src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs

@ -199,6 +199,25 @@ namespace OpenIddict.Core
return Task.FromResult(authorization.Subject);
}
/// <summary>
/// Retrieves the type associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</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 type associated with the specified authorization.
/// </returns>
public virtual Task<string> GetTypeAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return Task.FromResult(authorization.Type);
}
/// <summary>
/// Executes the specified query.
/// </summary>
@ -254,11 +273,37 @@ namespace OpenIddict.Core
/// </returns>
public virtual Task SetStatusAsync([NotNull] TAuthorization authorization, [NotNull] string status, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.Status = status;
return Task.FromResult(0);
}
/// <summary>
/// Sets the type associated with an authorization.
/// </summary>
/// <param name="authorization">The authorization.</param>
/// <param name="type">The type associated with the authorization.</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 SetTypeAsync([NotNull] TAuthorization authorization, [NotNull] string type, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
authorization.Type = type;
return Task.FromResult(0);
}
/// <summary>
/// Updates an existing authorization.
/// </summary>

10
src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs

@ -432,6 +432,11 @@ namespace OpenIddict.Core
public virtual Task SetExpirationDateAsync([NotNull] TToken token,
[CanBeNull] DateTimeOffset? date, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
token.ExpirationDate = date;
return Task.FromResult(0);
@ -448,6 +453,11 @@ namespace OpenIddict.Core
/// </returns>
public virtual Task SetStatusAsync([NotNull] TToken token, [NotNull] string status, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
token.Status = status;
return Task.FromResult(0);

75
src/OpenIddict.EntityFramework/OpenIddictExtensions.cs

@ -184,56 +184,85 @@ namespace Microsoft.Extensions.DependencyInjection
// Configure the TApplication entity.
builder.Entity<TApplication>()
.HasKey(application => application.Id);
.HasKey(application => application.Id);
builder.Entity<TApplication>()
.Property(application => application.ClientId)
.HasMaxLength(450)
.HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute()));
.Property(application => application.ClientId)
.IsRequired()
.HasMaxLength(450)
.HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute()));
builder.Entity<TApplication>()
.HasMany(application => application.Authorizations)
.WithOptional(authorization => authorization.Application)
.Map(association => association.MapKey("ApplicationId"));
.Property(application => application.Type)
.IsRequired();
builder.Entity<TApplication>()
.HasMany(application => application.Tokens)
.WithOptional(token => token.Application)
.Map(association => association.MapKey("ApplicationId"));
.HasMany(application => application.Authorizations)
.WithOptional(authorization => authorization.Application)
.Map(association => association.MapKey("ApplicationId"));
builder.Entity<TApplication>()
.ToTable("OpenIddictApplications");
.HasMany(application => application.Tokens)
.WithOptional(token => token.Application)
.Map(association => association.MapKey("ApplicationId"));
builder.Entity<TApplication>()
.ToTable("OpenIddictApplications");
// Configure the TAuthorization entity.
builder.Entity<TAuthorization>()
.HasKey(authorization => authorization.Id);
.HasKey(authorization => authorization.Id);
builder.Entity<TAuthorization>()
.Property(authorization => authorization.Status)
.IsRequired();
builder.Entity<TAuthorization>()
.Property(authorization => authorization.Subject)
.IsRequired();
builder.Entity<TAuthorization>()
.Property(authorization => authorization.Type)
.IsRequired();
builder.Entity<TAuthorization>()
.HasMany(application => application.Tokens)
.WithOptional(token => token.Authorization)
.Map(association => association.MapKey("AuthorizationId"));
.HasMany(application => application.Tokens)
.WithOptional(token => token.Authorization)
.Map(association => association.MapKey("AuthorizationId"));
builder.Entity<TAuthorization>()
.ToTable("OpenIddictAuthorizations");
.ToTable("OpenIddictAuthorizations");
// Configure the TScope entity.
builder.Entity<TScope>()
.HasKey(scope => scope.Id);
.HasKey(scope => scope.Id);
builder.Entity<TScope>()
.Property(scope => scope.Name)
.IsRequired();
builder.Entity<TScope>()
.ToTable("OpenIddictScopes");
.ToTable("OpenIddictScopes");
// Configure the TToken entity.
builder.Entity<TToken>()
.HasKey(token => token.Id);
.HasKey(token => token.Id);
builder.Entity<TToken>()
.Property(token => token.Hash)
.HasMaxLength(450)
.HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute()));
builder.Entity<TToken>()
.Property(token => token.Subject)
.IsRequired();
builder.Entity<TToken>()
.Property(token => token.Hash)
.HasMaxLength(450)
.HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute()));
.Property(token => token.Type)
.IsRequired();
builder.Entity<TToken>()
.ToTable("OpenIddictTokens");
.ToTable("OpenIddictTokens");
return builder;
}

3
src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs

@ -146,7 +146,8 @@ namespace OpenIddict.EntityFramework
var authorization = new TAuthorization
{
Status = descriptor.Status,
Subject = descriptor.Subject
Subject = descriptor.Subject,
Type = descriptor.Type
};
if (descriptor.Scopes.Count != 0)

24
src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs

@ -225,6 +225,12 @@ namespace Microsoft.Extensions.DependencyInjection
entity.HasIndex(application => application.ClientId)
.IsUnique(unique: true);
entity.Property(application => application.ClientId)
.IsRequired(required: true);
entity.Property(application => application.Type)
.IsRequired();
entity.HasMany(application => application.Authorizations)
.WithOne(authorization => authorization.Application)
.HasForeignKey("ApplicationId")
@ -243,6 +249,15 @@ namespace Microsoft.Extensions.DependencyInjection
{
entity.HasKey(authorization => authorization.Id);
entity.Property(authorization => authorization.Status)
.IsRequired();
entity.Property(authorization => authorization.Subject)
.IsRequired();
entity.Property(authorization => authorization.Type)
.IsRequired();
entity.HasMany(application => application.Tokens)
.WithOne(token => token.Authorization)
.HasForeignKey("AuthorizationId")
@ -256,6 +271,9 @@ namespace Microsoft.Extensions.DependencyInjection
{
entity.HasKey(scope => scope.Id);
entity.Property(scope => scope.Name)
.IsRequired();
entity.ToTable("OpenIddictScopes");
});
@ -267,6 +285,12 @@ namespace Microsoft.Extensions.DependencyInjection
entity.HasIndex(token => token.Hash)
.IsUnique(unique: true);
entity.Property(token => token.Subject)
.IsRequired();
entity.Property(token => token.Type)
.IsRequired();
entity.ToTable("OpenIddictTokens");
});

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

@ -145,7 +145,8 @@ namespace OpenIddict.EntityFrameworkCore
var authorization = new TAuthorization
{
Status = descriptor.Status,
Subject = descriptor.Subject
Subject = descriptor.Subject,
Type = descriptor.Type
};
if (descriptor.Scopes.Count != 0)

5
src/OpenIddict.Models/OpenIddictAuthorization.cs

@ -65,5 +65,10 @@ namespace OpenIddict.Models
/// associated with the current authorization.
/// </summary>
public virtual IList<TToken> Tokens { get; } = new List<TToken>();
/// <summary>
/// Gets or sets the type of the current authorization.
/// </summary>
public virtual string Type { get; set; }
}
}

20
src/OpenIddict/OpenIddictExtensions.cs

@ -168,14 +168,10 @@ namespace Microsoft.AspNetCore.Builder
"Reference tokens cannot be used when configuring JWT as the access token format.");
}
if (options.UseRollingTokens && options.DisableTokenRevocation)
if (options.UseSlidingExpiration && options.DisableTokenRevocation && !options.UseRollingTokens)
{
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.");
throw new InvalidOperationException("Sliding expiration must be disabled when turning off " +
"token revocation if rolling tokens are not used.");
}
// Ensure at least one asymmetric signing certificate/key was registered if the implicit flow was enabled.
@ -617,8 +613,8 @@ namespace Microsoft.AspNetCore.Builder
}
/// <summary>
/// Disables sliding expiration. When using this option, a single refresh token
/// is issued with a fixed expiration date: when it expires, a complete
/// Disables sliding expiration. When using this option, refresh tokens
/// are issued with a fixed expiration date: when it expires, a complete
/// authorization flow must be started to retrieve a new refresh token.
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
@ -1043,10 +1039,8 @@ namespace Microsoft.AspNetCore.Builder
/// <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).
/// Note: this option cannot be used when manually disabling sliding expiration.
/// a new refresh token is always issued for each refresh token request (and the previous
/// one is automatically revoked unless token revocation was explicitly disabled).
/// </summary>
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>

3
src/OpenIddict/OpenIddictOptions.cs

@ -99,7 +99,8 @@ namespace OpenIddict
/// 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.
/// refresh token request (and the previous one is automatically revoked
/// unless token revocation was explicitly disabled in the options).
/// </summary>
public bool UseRollingTokens { get; set; }
}

154
src/OpenIddict/OpenIddictProvider.Exchange.cs

@ -206,10 +206,15 @@ namespace OpenIddict
{
var options = (OpenIddictOptions) context.Options;
var authorizations = context.HttpContext.RequestServices.GetRequiredService<OpenIddictAuthorizationManager<TAuthorization>>();
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var tokens = context.HttpContext.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
if (context.Ticket != null)
{
// Store the authentication ticket as a request property so it can be later retrieved, if necessary.
context.Request.SetProperty(OpenIddictConstants.Properties.AuthenticationTicket, context.Ticket);
}
if (options.DisableTokenRevocation || (!context.Request.IsAuthorizationCodeGrantType() &&
!context.Request.IsRefreshTokenGrantType()))
{
@ -223,127 +228,58 @@ namespace OpenIddict
Debug.Assert(context.Ticket != null, "The authentication ticket shouldn't be null.");
// Extract the token identifier from the authentication ticket.
var identifier = context.Ticket.GetProperty(OpenIdConnectConstants.Properties.TokenId);
Debug.Assert(!string.IsNullOrEmpty(identifier), "The authentication ticket should contain a ticket identifier.");
var identifier = context.Ticket.GetTokenId();
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/refresh token from the database and ensure it is still valid.
var token = await tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
if (token == null)
{
// Retrieve the authorization code from the database and ensure it is still valid.
var token = await tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
if (token == null)
{
logger.LogError("The token request was rejected because the authorization " +
"code '{Identifier}' was not found in the database.", identifier);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The specified authorization code is no longer valid.");
return;
}
// If the authorization code is already marked as redeemed, this may indicate that the authorization
// code was compromised. In this case, revoke the authorization and all the associated tokens.
// See https://tools.ietf.org/html/rfc6749#section-10.5 for more information.
if (await tokens.IsRedeemedAsync(token, context.HttpContext.RequestAborted))
{
var key = context.Ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId);
if (!string.IsNullOrEmpty(key))
{
var authorization = await authorizations.FindByIdAsync(key, context.HttpContext.RequestAborted);
if (authorization != null)
{
logger.LogInformation("The authorization '{Identifier}' was automatically revoked.", key);
await authorizations.RevokeAsync(authorization, context.HttpContext.RequestAborted);
}
var items = await tokens.FindByAuthorizationIdAsync(key, context.HttpContext.RequestAborted);
for (var index = 0; index < items.Length; index++)
{
logger.LogInformation("The compromised token '{Identifier}' was automatically revoked.",
await tokens.GetIdAsync(items[index], context.HttpContext.RequestAborted));
await tokens.RevokeAsync(items[index], context.HttpContext.RequestAborted);
}
}
logger.LogError("The token request was rejected because the authorization code " +
"'{Identifier}' has already been redeemed.", identifier);
logger.LogError("The token request was rejected because the authorization code " +
"or refresh token '{Identifier}' was not found in the database.", identifier);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The specified authorization code has already been redeemed.");
return;
}
else if (!await tokens.IsValidAsync(token, context.HttpContext.RequestAborted))
{
logger.LogError("The token request was rejected because the authorization code " +
"'{Identifier}' was no longer valid.", identifier);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The specified authorization code is no longer valid.");
return;
}
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: context.Request.IsAuthorizationCodeGrantType() ?
"The specified authorization code is no longer valid." :
"The specified refresh token is no longer valid.");
// Mark the authorization code as redeemed to prevent token reuse.
await tokens.RedeemAsync(token, context.HttpContext.RequestAborted);
return;
}
else
// If the authorization code/refresh token is already marked as redeemed, this may indicate that
// it was compromised. In this case, revoke the authorization and all the associated tokens.
// See https://tools.ietf.org/html/rfc6749#section-10.5 for more information.
if (await tokens.IsRedeemedAsync(token, context.HttpContext.RequestAborted))
{
// Retrieve the token from the database and ensure it is still valid.
var token = await tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
if (token == null)
{
logger.LogError("The token request was rejected because the refresh token " +
"'{Identifier}' was not found in the database.", identifier);
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 " +
"'{Identifier}' has already been redeemed.", identifier);
await RevokeAuthorizationAsync(context.Ticket, context.HttpContext);
await RevokeTokensAsync(context.Ticket, context.HttpContext);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The specified refresh token has already been redeemed.");
logger.LogError("The token request was rejected because the authorization code " +
"or refresh token '{Identifier}' has already been redeemed.", identifier);
return;
}
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: context.Request.IsAuthorizationCodeGrantType() ?
"The specified authorization code has already been redeemed." :
"The specified refresh token has already been redeemed.");
else if (!await tokens.IsValidAsync(token, context.HttpContext.RequestAborted))
{
logger.LogError("The token request was rejected because the refresh token " +
"'{Identifier}' was no longer valid.", identifier);
return;
}
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The specified refresh token is no longer valid.");
else if (!await tokens.IsValidAsync(token, context.HttpContext.RequestAborted))
{
logger.LogError("The token request was rejected because the authorization code " +
"or refresh token '{Identifier}' was no longer valid.", identifier);
return;
}
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: context.Request.IsAuthorizationCodeGrantType() ?
"The specified authorization code is no longer valid." :
"The specified refresh token is no longer valid.");
// 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.UseRollingTokens)
{
await tokens.RedeemAsync(token, context.HttpContext.RequestAborted);
}
return;
}
// Invoke the rest of the pipeline to allow

365
src/OpenIddict/OpenIddictProvider.Helpers.cs

@ -0,0 +1,365 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Core;
namespace OpenIddict
{
public partial class OpenIddictProvider<TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
where TApplication : class where TAuthorization : class where TScope : class where TToken : class
{
private async Task CreateAuthorizationAsync(
[NotNull] AuthenticationTicket ticket, [NotNull] OpenIddictOptions options,
[NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request)
{
var applications = context.RequestServices.GetRequiredService<OpenIddictApplicationManager<TApplication>>();
var authorizations = context.RequestServices.GetRequiredService<OpenIddictAuthorizationManager<TAuthorization>>();
var logger = context.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
if (options.DisableTokenRevocation)
{
return;
}
var descriptor = new OpenIddictAuthorizationDescriptor
{
Status = OpenIddictConstants.Statuses.Valid,
Subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject),
Type = OpenIddictConstants.AuthorizationTypes.AdHoc
};
foreach (var scope in request.GetScopes())
{
descriptor.Scopes.Add(scope);
}
// If the client application is known, bind it to the authorization.
if (!string.IsNullOrEmpty(request.ClientId))
{
var application = await applications.FindByClientIdAsync(request.ClientId, context.RequestAborted);
if (application == null)
{
throw new InvalidOperationException("The client application cannot be retrieved from the database.");
}
descriptor.ApplicationId = await applications.GetIdAsync(application, context.RequestAborted);
}
var authorization = await authorizations.CreateAsync(descriptor, context.RequestAborted);
if (authorization != null)
{
var identifier = await authorizations.GetIdAsync(authorization, context.RequestAborted);
if (string.IsNullOrEmpty(request.ClientId))
{
logger.LogInformation("An ad hoc authorization was automatically created and " +
"associated with an unknown application: {Identifier}.", identifier);
}
else
{
logger.LogInformation("An ad hoc authorization was automatically created and " +
"associated with the '{ClientId}' application: {Identifier}.",
request.ClientId, identifier);
}
// Attach the unique identifier of the ad hoc authorization to the authentication ticket
// so that it is attached to all the derived tokens, allowing batched revocations support.
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, identifier);
}
}
private async Task<string> CreateTokenAsync(
[NotNull] string type, [NotNull] AuthenticationTicket ticket,
[NotNull] OpenIddictOptions options, [NotNull] HttpContext context,
[NotNull] OpenIdConnectRequest request,
[NotNull] ISecureDataFormat<AuthenticationTicket> format)
{
Debug.Assert(!(options.DisableTokenRevocation && options.UseReferenceTokens),
"Token revocation cannot be disabled when using reference tokens.");
Debug.Assert(type == OpenIdConnectConstants.TokenUsages.AccessToken ||
type == OpenIdConnectConstants.TokenUsages.AuthorizationCode ||
type == OpenIdConnectConstants.TokenUsages.RefreshToken,
"Only authorization codes, access and refresh tokens should be created using this method.");
var applications = context.RequestServices.GetRequiredService<OpenIddictApplicationManager<TApplication>>();
var logger = context.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var tokens = context.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
// When sliding expiration is disabled, the expiration date of generated refresh tokens is fixed
// and must exactly match the expiration date of the refresh token used in the token request.
if (request.IsTokenRequest() && request.IsRefreshTokenGrantType() &&
!options.UseSlidingExpiration && type == OpenIdConnectConstants.TokenUsages.RefreshToken)
{
var properties = request.GetProperty<AuthenticationTicket>(
OpenIddictConstants.Properties.AuthenticationTicket)?.Properties;
Debug.Assert(properties != null, "The authentication properties shouldn't be null.");
ticket.Properties.ExpiresUtc = properties.ExpiresUtc;
}
if (options.DisableTokenRevocation)
{
return null;
}
var descriptor = new OpenIddictTokenDescriptor
{
AuthorizationId = ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId),
CreationDate = ticket.Properties.IssuedUtc,
ExpirationDate = ticket.Properties.ExpiresUtc,
Status = OpenIddictConstants.Statuses.Valid,
Subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject),
Type = type
};
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(OpenIddictConstants.Properties.AuthorizationId)
.RemoveProperty(OpenIdConnectConstants.Properties.TokenId);
}
// If reference tokens are enabled, create a new entry for
// authorization codes, refresh tokens and access tokens.
if (options.UseReferenceTokens)
{
// 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.
descriptor.Ciphertext = format.Protect(ticket);
// Generate a new crypto-secure random identifier that will be
// substituted to the ciphertext returned by the data format.
var bytes = new byte[256 / 8];
options.RandomNumberGenerator.GetBytes(bytes);
result = Base64UrlEncoder.Encode(bytes);
// Compute the digest of the generated identifier and use
// 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.
using (var algorithm = SHA256.Create())
{
descriptor.Hash = Convert.ToBase64String(algorithm.ComputeHash(bytes));
}
}
// Otherwise, only create a token metadata entry for authorization codes and refresh tokens.
else if (type != OpenIdConnectConstants.TokenUsages.AuthorizationCode &&
type != OpenIdConnectConstants.TokenUsages.RefreshToken)
{
return null;
}
// If the client application is known, associate it with the token.
if (!string.IsNullOrEmpty(request.ClientId))
{
var application = await applications.FindByClientIdAsync(request.ClientId, context.RequestAborted);
if (application == null)
{
throw new InvalidOperationException("The client application cannot be retrieved from the database.");
}
descriptor.ApplicationId = await applications.GetIdAsync(application, context.RequestAborted);
}
// 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);
// Restore the authorization identifier using the identifier attached with the database entry.
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId,
await tokens.GetAuthorizationIdAsync(token, context.RequestAborted));
if (!string.IsNullOrEmpty(result))
{
logger.LogTrace("A new reference token was successfully generated and persisted " +
"in the database: {Token} ; {Claims} ; {Properties}.",
result, ticket.Principal.Claims, ticket.Properties.Items);
}
return result;
}
private async Task<AuthenticationTicket> ReceiveTokenAsync(
[NotNull] string value, [NotNull] OpenIddictOptions options,
[NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request,
[NotNull] ISecureDataFormat<AuthenticationTicket> format)
{
var logger = context.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var tokens = context.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
if (!options.UseReferenceTokens)
{
return null;
}
string hash;
try
{
// Compute the digest of the received token and use it
// to retrieve the reference token from the database.
using (var algorithm = SHA256.Create())
{
hash = Convert.ToBase64String(algorithm.ComputeHash(Base64UrlEncoder.DecodeBytes(value)));
}
}
// Swallow format-related exceptions to ensure badly formed
// or tampered tokens don't cause an exception at this stage.
catch
{
return null;
}
// Retrieve the token entry from the database. If it
// cannot be found, assume the token is not valid.
var token = await tokens.FindByHashAsync(hash, context.RequestAborted);
if (token == null)
{
logger.LogInformation("The reference token corresponding to the '{Hash}' hashed " +
"identifier cannot be found in the database.", hash);
return null;
}
var identifier = await tokens.GetIdAsync(token, context.RequestAborted);
if (string.IsNullOrEmpty(identifier))
{
logger.LogWarning("The identifier associated with the received token cannot be retrieved. " +
"This may indicate that the token entry is corrupted.");
return null;
}
// 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, context.RequestAborted);
if (string.IsNullOrEmpty(ciphertext))
{
logger.LogWarning("The ciphertext associated with the token '{Identifier}' cannot be retrieved. " +
"This may indicate that the token is not a reference token.", identifier);
return null;
}
var ticket = format.Unprotect(ciphertext);
if (ticket == null)
{
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, context.RequestAborted));
return null;
}
// 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);
// Restore the authorization identifier using the identifier attached with the database entry.
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId,
await tokens.GetAuthorizationIdAsync(token, context.RequestAborted));
logger.LogTrace("The reference token '{Identifier}' was successfully retrieved " +
"from the database and decrypted: {Claims} ; {Properties}.",
identifier, ticket.Principal.Claims, ticket.Properties.Items);
return ticket;
}
private async Task RevokeAuthorizationAsync([NotNull] AuthenticationTicket ticket, [NotNull] HttpContext context)
{
var authorizations = context.RequestServices.GetRequiredService<OpenIddictAuthorizationManager<TAuthorization>>();
var logger = context.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var identifier = ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId);
if (string.IsNullOrEmpty(identifier))
{
return;
}
var authorization = await authorizations.FindByIdAsync(identifier, context.RequestAborted);
if (authorization == null)
{
return;
}
await authorizations.RevokeAsync(authorization, context.RequestAborted);
logger.LogInformation("The authorization '{Identifier}' was automatically revoked.", identifier);
}
private async Task RevokeTokensAsync([NotNull] AuthenticationTicket ticket, [NotNull] HttpContext context)
{
var logger = context.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var tokens = context.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
var identifier = ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId);
if (string.IsNullOrEmpty(identifier))
{
return;
}
foreach (var token in await tokens.FindByAuthorizationIdAsync(identifier, context.RequestAborted))
{
// Don't overwrite the status of the token used in the token request.
if (string.Equals(ticket.GetTokenId(), await tokens.GetIdAsync(token, context.RequestAborted)))
{
continue;
}
await tokens.RevokeAsync(token, context.RequestAborted);
logger.LogInformation("The token '{Identifier}' was automatically revoked.",
await tokens.GetIdAsync(token, context.RequestAborted));
}
}
}
}

303
src/OpenIddict/OpenIddictProvider.Serialization.cs

@ -4,20 +4,11 @@
* the license and the contributors participating to this project.
*/
using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Core;
namespace OpenIddict
{
@ -141,39 +132,11 @@ namespace OpenIddict
public override async Task SerializeRefreshToken([NotNull] SerializeRefreshTokenContext context)
{
var options = (OpenIddictOptions) context.Options;
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var tokens = context.HttpContext.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
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.HandleResponse();
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, options,
OpenIdConnectConstants.TokenUsages.RefreshToken,
context.Ticket, (OpenIddictOptions) context.Options,
context.HttpContext, context.Request, context.DataFormat);
// If a reference token was returned by CreateTokenAsync(),
@ -187,267 +150,5 @@ namespace OpenIddict
// Otherwise, let the OpenID Connect server middleware
// serialize the token using its default internal logic.
}
private async Task<string> CreateTokenAsync(
[NotNull] string type, [NotNull] AuthenticationTicket ticket,
[NotNull] OpenIddictOptions options, [NotNull] HttpContext context,
[NotNull] OpenIdConnectRequest request,
[NotNull] ISecureDataFormat<AuthenticationTicket> format)
{
var applications = context.RequestServices.GetRequiredService<OpenIddictApplicationManager<TApplication>>();
var authorizations = context.RequestServices.GetRequiredService<OpenIddictAuthorizationManager<TAuthorization>>();
var logger = context.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var tokens = context.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
Debug.Assert(!(options.DisableTokenRevocation && options.UseReferenceTokens),
"Token revocation cannot be disabled when using reference tokens.");
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)
{
return null;
}
var descriptor = new OpenIddictTokenDescriptor
{
CreationDate = ticket.Properties.IssuedUtc,
ExpirationDate = ticket.Properties.ExpiresUtc,
Status = OpenIddictConstants.Statuses.Valid,
Subject = ticket.Principal.GetClaim(OpenIdConnectConstants.Claims.Subject),
Type = type
};
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)
{
// 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.
descriptor.Ciphertext = format.Protect(ticket);
// Generate a new crypto-secure random identifier that will be
// substituted to the ciphertext returned by the data format.
var bytes = new byte[256 / 8];
options.RandomNumberGenerator.GetBytes(bytes);
result = Base64UrlEncoder.Encode(bytes);
// Compute the digest of the generated identifier and use
// 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.
using (var algorithm = SHA256.Create())
{
descriptor.Hash = Convert.ToBase64String(algorithm.ComputeHash(bytes));
}
}
// Otherwise, only create a token metadata entry for authorization codes and refresh tokens.
else if (type != OpenIdConnectConstants.TokenUsages.AuthorizationCode &&
type != OpenIdConnectConstants.TokenUsages.RefreshToken)
{
return null;
}
// If the client application is known, associate it with the token.
if (!string.IsNullOrEmpty(request.ClientId))
{
var application = await applications.FindByClientIdAsync(request.ClientId, context.RequestAborted);
if (application == null)
{
throw new InvalidOperationException("The client application cannot be retrieved from the database.");
}
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))
{
descriptor.AuthorizationId = ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId);
}
// Otherwise, create an ad-hoc authorization if the token is an authorization code.
else if (type == OpenIdConnectConstants.TokenUsages.AuthorizationCode)
{
Debug.Assert(!string.IsNullOrEmpty(descriptor.ApplicationId), "The client identifier shouldn't be null.");
var authorization = await CreateAuthorizationAsync(descriptor, context, request);
if (authorization != null)
{
descriptor.AuthorizationId = await authorizations.GetIdAsync(authorization, 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 " +
"in the database: {Token} ; {Claims} ; {Properties}.",
result, ticket.Principal.Claims, ticket.Properties.Items);
}
return result;
}
private async Task<AuthenticationTicket> ReceiveTokenAsync(
[NotNull] string value, [NotNull] OpenIddictOptions options,
[NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request,
[NotNull] ISecureDataFormat<AuthenticationTicket> format)
{
var logger = context.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var tokens = context.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
if (!options.UseReferenceTokens)
{
return null;
}
string hash;
try
{
// Compute the digest of the received token and use it
// to retrieve the reference token from the database.
using (var algorithm = SHA256.Create())
{
hash = Convert.ToBase64String(algorithm.ComputeHash(Base64UrlEncoder.DecodeBytes(value)));
}
}
// Swallow format-related exceptions to ensure badly formed
// or tampered tokens don't cause an exception at this stage.
catch
{
return null;
}
// Retrieve the token entry from the database. If it
// cannot be found, assume the token is not valid.
var token = await tokens.FindByHashAsync(hash, context.RequestAborted);
if (token == null)
{
logger.LogInformation("The reference token corresponding to the '{Hash}' hashed " +
"identifier cannot be found in the database.", hash);
return null;
}
var identifier = await tokens.GetIdAsync(token, context.RequestAborted);
if (string.IsNullOrEmpty(identifier))
{
logger.LogWarning("The identifier associated with the received token cannot be retrieved. " +
"This may indicate that the token entry is corrupted.");
return null;
}
// 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, context.RequestAborted);
if (string.IsNullOrEmpty(ciphertext))
{
logger.LogWarning("The ciphertext associated with the token '{Identifier}' cannot be retrieved. " +
"This may indicate that the token is not a reference token.", identifier);
return null;
}
var ticket = format.Unprotect(ciphertext);
if (ticket == null)
{
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, context.RequestAborted));
return null;
}
// 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);
// 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))
{
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId,
await tokens.GetAuthorizationIdAsync(token, context.RequestAborted));
}
logger.LogTrace("The reference token '{Identifier}' was successfully retrieved " +
"from the database and decrypted: {Claims} ; {Properties}.",
identifier, ticket.Principal.Claims, ticket.Properties.Items);
return ticket;
}
private Task<TAuthorization> CreateAuthorizationAsync(
[NotNull] OpenIddictTokenDescriptor token,
[NotNull] HttpContext context, [NotNull] OpenIdConnectRequest request)
{
var authorizations = context.RequestServices.GetRequiredService<OpenIddictAuthorizationManager<TAuthorization>>();
var descriptor = new OpenIddictAuthorizationDescriptor
{
ApplicationId = token.ApplicationId,
Status = OpenIddictConstants.Statuses.Valid,
Subject = token.Subject
};
foreach (var scope in request.GetScopes())
{
descriptor.Scopes.Add(scope);
}
return authorizations.CreateAsync(descriptor, context.RequestAborted);
}
}
}

146
src/OpenIddict/OpenIddictProvider.Signin.cs

@ -0,0 +1,146 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System.Diagnostics;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OpenIddict.Core;
namespace OpenIddict
{
public partial class OpenIddictProvider<TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
where TApplication : class where TAuthorization : class where TScope : class where TToken : class
{
public override async Task ProcessSigninResponse([NotNull] ProcessSigninResponseContext context)
{
var options = (OpenIddictOptions) context.Options;
var logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var tokens = context.HttpContext.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
if (context.Request.IsTokenRequest() && (context.Request.IsAuthorizationCodeGrantType() ||
context.Request.IsRefreshTokenGrantType()))
{
// Note: when handling a grant_type=authorization_code or refresh_token request,
// the OpenID Connect server middleware allows creating authentication tickets
// that are completely disconnected from the original code or refresh token ticket.
// This scenario is deliberately not supported in OpenIddict and all the tickets
// must be linked. To ensure the properties are preserved from an authorization code
// or a refresh token to the new ticket, they are manually restored if necessary.
// Retrieve the original authentication ticket from the request properties.
var ticket = context.Request.GetProperty<AuthenticationTicket>(
OpenIddictConstants.Properties.AuthenticationTicket);
Debug.Assert(ticket != null, "The authentication ticket shouldn't be null.");
// If the properties instances of the two authentication tickets differ,
// restore the missing properties in the new authentication ticket.
if (!ReferenceEquals(ticket.Properties, context.Ticket.Properties))
{
foreach (var property in ticket.Properties.Items)
{
// Don't override the properties that have been
// manually set on the new authentication ticket.
if (context.Ticket.HasProperty(property.Key))
{
continue;
}
context.Ticket.AddProperty(property.Key, property.Value);
}
// Always include the "openid" scope when the developer doesn't explicitly call SetScopes.
// Note: the application is allowed to specify a different "scopes": in this case,
// don't replace the "scopes" property stored in the authentication ticket.
if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OpenId) && !context.Ticket.HasScope())
{
context.Ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId);
}
context.IncludeIdentityToken = context.Ticket.HasScope(OpenIdConnectConstants.Scopes.OpenId);
}
// Always include a refresh token for grant_type=refresh_token requests if
// rolling tokens are enabled and if the offline_access scope was specified.
context.IncludeRefreshToken = context.Request.IsRefreshTokenGrantType() && options.UseRollingTokens &&
context.Ticket.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess);
// If token revocation was explicitly disabled,
// none of the following security routines apply.
if (options.DisableTokenRevocation)
{
return;
}
// Extract the token identifier from the authentication ticket.
var identifier = context.Ticket.GetTokenId();
Debug.Assert(!string.IsNullOrEmpty(identifier),
"The authentication ticket should contain a ticket identifier.");
// If rolling tokens are enabled or if the request is a grant_type=authorization_code request,
// mark the authorization code or the refresh token as redeemed to prevent future reuses.
// See https://tools.ietf.org/html/rfc6749#section-6 for more information.
if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType())
{
var token = await tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
if (token != null)
{
await tokens.RedeemAsync(token, context.HttpContext.RequestAborted);
logger.LogInformation("The token '{Identifier}' was automatically marked as redeemed.", identifier);
}
}
// When rolling tokens are enabled, revoke all the previously issued tokens associated
// with the authorization if the request is a grant_type=refresh_token request.
if (options.UseRollingTokens && context.Request.IsRefreshTokenGrantType())
{
await RevokeTokensAsync(context.Ticket, context.HttpContext);
}
// When rolling tokens are disabled, extend the expiration date
// of the existing token instead of returning a new refresh token
// with a new expiration date if sliding expiration was not disabled.
else if (options.UseSlidingExpiration && context.Request.IsRefreshTokenGrantType())
{
var token = await tokens.FindByIdAsync(identifier, context.HttpContext.RequestAborted);
if (token != null)
{
// Compute the new expiration date of the refresh token.
var date = context.Options.SystemClock.UtcNow +
(context.Ticket.GetRefreshTokenLifetime() ??
context.Options.RefreshTokenLifetime);
await tokens.ExtendAsync(token, date, context.HttpContext.RequestAborted);
logger.LogInformation("The expiration date of the refresh token '{Identifier}' " +
"was automatically updated: {Date}.", identifier, date);
context.IncludeRefreshToken = false;
}
// If the refresh token entry could not be
// found in the database, generate a new one.
}
}
// If no authorization was explicitly attached to the authentication ticket,
// create an ad hoc authorization if an authorization code or a refresh token
// is going to be returned to the client application as part of the response.
if (!context.Ticket.HasProperty(OpenIddictConstants.Properties.AuthorizationId) &&
(context.IncludeAuthorizationCode || context.IncludeRefreshToken))
{
await CreateAuthorizationAsync(context.Ticket, options, context.HttpContext, context.Request);
}
}
}
}

29
test/OpenIddict.Tests/OpenIddictExtensionsTests.cs

@ -177,7 +177,7 @@ namespace OpenIddict.Tests
}
[Fact]
public void UseOpenIddict_ThrowsAnExceptionWhenUsingRollingTokensWithTokenRevocationDisabled()
public void UseOpenIddict_ThrowsAnExceptionWhenUsingSlidingExpirationWithoutRollingTokensAndWithTokenRevocationDisabled()
{
// Arrange
var services = new ServiceCollection();
@ -186,36 +186,15 @@ namespace OpenIddict.Tests
services.AddOpenIddict()
.EnableAuthorizationEndpoint("/connect/authorize")
.AllowImplicitFlow()
.DisableTokenRevocation()
.UseRollingTokens();
var builder = new ApplicationBuilder(services.BuildServiceProvider());
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddict());
Assert.Equal("Rolling tokens cannot be used when disabling token expiration.", exception.Message);
}
[Fact]
public void UseOpenIddict_ThrowsAnExceptionWhenUsingRollingTokensWithSlidingExpirationDisabled()
{
// Arrange
var services = new ServiceCollection();
services.AddDataProtection();
services.AddOpenIddict()
.EnableAuthorizationEndpoint("/connect/authorize")
.AllowImplicitFlow()
.UseRollingTokens()
.Configure(options => options.UseSlidingExpiration = false);
.DisableTokenRevocation();
var builder = new ApplicationBuilder(services.BuildServiceProvider());
// Act and assert
var exception = Assert.Throws<InvalidOperationException>(() => builder.UseOpenIddict());
Assert.Equal("Rolling tokens cannot be used without enabling sliding expiration.", exception.Message);
Assert.Equal("Sliding expiration must be disabled when turning off " +
"token revocation if rolling tokens are not used.", exception.Message);
}
[Fact]

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

@ -438,6 +438,7 @@ namespace OpenIddict.Tests
builder.Configure(options => options.RevocationEndpointPath = PathString.Empty);
builder.DisableTokenRevocation();
builder.DisableSlidingExpiration();
});
var client = new OpenIdConnectClient(server.CreateClient());
@ -489,6 +490,7 @@ namespace OpenIddict.Tests
builder.Configure(options => options.RevocationEndpointPath = PathString.Empty);
builder.DisableTokenRevocation();
builder.DisableSlidingExpiration();
});
var client = new OpenIdConnectClient(server.CreateClient());
@ -564,6 +566,63 @@ namespace OpenIddict.Tests
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsUnknown()
{
// 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 manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()))
.ReturnsAsync(value: null);
});
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 is no longer valid.", response.ErrorDescription);
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationCodeIsAlreadyRedeemed()
{
@ -631,7 +690,7 @@ namespace OpenIddict.Tests
}
[Fact]
public async Task HandleTokenRequest_RevokesTokensWhenAuthorizationCodeIsAlreadyRedeemed()
public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsAlreadyRedeemed()
{
// Arrange
var ticket = new AuthenticationTicket(
@ -639,33 +698,23 @@ namespace OpenIddict.Tests
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetPresenters("Fabrikam");
ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.AuthorizationCode);
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, "18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA"))
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
var tokens = new[]
{
new OpenIddictToken(),
new OpenIddictToken(),
new OpenIddictToken()
};
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(tokens[0]);
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.IsRedeemedAsync(tokens[0], It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(tokens);
});
var server = CreateAuthorizationServer(builder =>
@ -681,17 +730,9 @@ namespace OpenIddict.Tests
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(CreateAuthorizationManager(instance =>
{
var authorization = new OpenIddictAuthorization();
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(authorization);
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.AuthorizationCodeFormat = format.Object);
builder.Configure(options => options.RefreshTokenFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
@ -699,25 +740,20 @@ namespace OpenIddict.Tests
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest
{
ClientId = "Fabrikam",
Code = "SplxlOBeZQQYbYS6WxSbIA",
GrantType = OpenIdConnectConstants.GrantTypes.AuthorizationCode,
RedirectUri = "http://www.fabrikam.com/path"
GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken,
RefreshToken = "8xLOxBtZp8"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The specified authorization code has already been redeemed.", response.ErrorDescription);
Assert.Equal("The specified refresh token 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());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[0], It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[1], It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[2], It.IsAny<CancellationToken>()), Times.Once());
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_RequestIsRejectedWhenAuthorizationCodeIsInvalid()
public async Task HandleTokenRequest_RevokesAuthorizationWhenAuthorizationCodeIsAlreadyRedeemed()
{
// Arrange
var ticket = new AuthenticationTicket(
@ -728,24 +764,19 @@ namespace OpenIddict.Tests
ticket.SetPresenters("Fabrikam");
ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.AuthorizationCode);
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, "18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA"))
.Returns(ticket);
var token = new OpenIddictToken();
var authorization = new OpenIddictAuthorization();
var manager = CreateTokenManager(instance =>
var manager = CreateAuthorizationManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", 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);
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(authorization);
});
var server = CreateAuthorizationServer(builder =>
@ -761,6 +792,17 @@ namespace OpenIddict.Tests
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
var token = new OpenIddictToken();
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.AuthorizationCodeFormat = format.Object);
@ -779,15 +821,14 @@ namespace OpenIddict.Tests
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The specified authorization code is no longer valid.", 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());
Mock.Get(manager).Verify(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(authorization, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsUnknown()
public async Task HandleTokenRequest_RevokesAuthorizationWhenRefreshTokenIsAlreadyRedeemed()
{
// Arrange
var ticket = new AuthenticationTicket(
@ -795,18 +836,21 @@ namespace OpenIddict.Tests
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, "18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
var manager = CreateTokenManager(instance =>
var authorization = new OpenIddictAuthorization();
var manager = CreateAuthorizationManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()))
.ReturnsAsync(value: null);
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(authorization);
});
var server = CreateAuthorizationServer(builder =>
@ -822,6 +866,17 @@ namespace OpenIddict.Tests
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(CreateTokenManager(instance =>
{
var token = new OpenIddictToken();
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.RefreshTokenFormat = format.Object);
@ -838,13 +893,14 @@ namespace OpenIddict.Tests
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The specified refresh token is no longer valid.", response.ErrorDescription);
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.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(authorization, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsAlreadyRedeemed()
public async Task HandleTokenRequest_RevokesTokensWhenAuthorizationCodeIsAlreadyRedeemed()
{
// Arrange
var ticket = new AuthenticationTicket(
@ -852,23 +908,33 @@ namespace OpenIddict.Tests
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetPresenters("Fabrikam");
ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.AuthorizationCode);
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, "18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA"))
.Returns(ticket);
var token = new OpenIddictToken();
var tokens = new[]
{
new OpenIddictToken(),
new OpenIddictToken(),
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.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(tokens[0]);
instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()))
instance.Setup(mock => mock.IsRedeemedAsync(tokens[0], It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(tokens);
});
var server = CreateAuthorizationServer(builder =>
@ -884,9 +950,17 @@ namespace OpenIddict.Tests
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(CreateAuthorizationManager(instance =>
{
var authorization = new OpenIddictAuthorization();
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(authorization);
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.RefreshTokenFormat = format.Object);
builder.Configure(options => options.AuthorizationCodeFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
@ -894,20 +968,25 @@ namespace OpenIddict.Tests
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest
{
GrantType = OpenIdConnectConstants.GrantTypes.RefreshToken,
RefreshToken = "8xLOxBtZp8"
ClientId = "Fabrikam",
Code = "SplxlOBeZQQYbYS6WxSbIA",
GrantType = OpenIdConnectConstants.GrantTypes.AuthorizationCode,
RedirectUri = "http://www.fabrikam.com/path"
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The specified refresh token has already been redeemed.", response.ErrorDescription);
Assert.Equal("The specified authorization code 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());
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());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[0], It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[1], It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[2], It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsInvalid()
public async Task HandleTokenRequest_RevokesTokensWhenRefreshTokenIsAlreadyRedeemed()
{
// Arrange
var ticket = new AuthenticationTicket(
@ -915,26 +994,32 @@ namespace OpenIddict.Tests
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, "18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
var token = new OpenIddictToken();
var tokens = new[]
{
new OpenIddictToken(),
new OpenIddictToken(),
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.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(tokens[0]);
instance.Setup(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.IsRedeemedAsync(tokens[0], It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(tokens);
});
var server = CreateAuthorizationServer(builder =>
@ -950,6 +1035,14 @@ namespace OpenIddict.Tests
.ReturnsAsync(OpenIddictConstants.ClientTypes.Public);
}));
builder.Services.AddSingleton(CreateAuthorizationManager(instance =>
{
var authorization = new OpenIddictAuthorization();
instance.Setup(mock => mock.FindByIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(authorization);
}));
builder.Services.AddSingleton(manager);
builder.Configure(options => options.RefreshTokenFormat = format.Object);
@ -966,21 +1059,21 @@ namespace OpenIddict.Tests
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The specified refresh token is no longer valid.", response.ErrorDescription);
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.IsValidAsync(token, It.IsAny<CancellationToken>()), Times.Once());
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());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[0], It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[1], It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[2], It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_AuthorizationCodeIsAutomaticallyRedeemed()
public async Task HandleTokenRequest_RequestIsRejectedWhenAuthorizationCodeIsInvalid()
{
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
@ -1000,8 +1093,11 @@ namespace OpenIddict.Tests
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", 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(true);
.ReturnsAsync(false);
});
var server = CreateAuthorizationServer(builder =>
@ -1034,19 +1130,20 @@ namespace OpenIddict.Tests
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The specified authorization code is no longer valid.", 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.RedeemAsync(token, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.IsRedeemedAsync(token, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task HandleTokenRequest_RefreshTokenIsAutomaticallyRedeemedWhenRollingTokensAreEnabled()
public async Task HandleTokenRequest_RequestIsRejectedWhenRefreshTokenIsInvalid()
{
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
@ -1069,7 +1166,7 @@ namespace OpenIddict.Tests
.ReturnsAsync(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
.ReturnsAsync(false);
});
var server = CreateAuthorizationServer(builder =>
@ -1087,8 +1184,6 @@ namespace OpenIddict.Tests
builder.Services.AddSingleton(manager);
builder.UseRollingTokens();
builder.Configure(options => options.RefreshTokenFormat = format.Object);
});
@ -1102,8 +1197,11 @@ namespace OpenIddict.Tests
});
// Assert
Assert.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The specified refresh token is no longer valid.", 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.RedeemAsync(token, It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()), Times.Once());
}
[Theory]

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

@ -274,6 +274,7 @@ namespace OpenIddict.Tests
builder.Configure(options => options.RevocationEndpointPath = PathString.Empty);
builder.DisableTokenRevocation();
builder.DisableSlidingExpiration();
});
var client = new OpenIdConnectClient(server.CreateClient());
@ -330,6 +331,7 @@ namespace OpenIddict.Tests
builder.Configure(options => options.RevocationEndpointPath = PathString.Empty);
builder.DisableTokenRevocation();
builder.DisableSlidingExpiration();
});
var client = new OpenIdConnectClient(server.CreateClient());

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

File diff suppressed because it is too large

723
test/OpenIddict.Tests/OpenIddictProviderTests.Signin.cs

@ -0,0 +1,723 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Client;
using AspNet.Security.OpenIdConnect.Extensions;
using AspNet.Security.OpenIdConnect.Primitives;
using AspNet.Security.OpenIdConnect.Server;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using OpenIddict.Core;
using OpenIddict.Models;
using Xunit;
namespace OpenIddict.Tests
{
public partial class OpenIddictProviderTests
{
[Fact]
public async Task ProcessSigninResponse_AuthenticationPropertiesAreAutomaticallyRestored()
{
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess);
ticket.SetProperty("custom_property_in_original_ticket", "original_value");
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Protect(It.IsAny<AuthenticationTicket>()))
.Returns("8xLOxBtZp8");
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(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(manager);
builder.UseRollingTokens();
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",
["do-not-flow-original-properties"] = true
});
// Assert
Assert.NotNull(response.IdToken);
Assert.NotNull(response.RefreshToken);
format.Verify(mock => mock.Protect(
It.Is<AuthenticationTicket>(value =>
value.Properties.Items["custom_property_in_original_ticket"] == "original_value" &&
value.Properties.Items["custom_property_in_new_ticket"] == "new_value")));
}
[Fact]
public async Task ProcessSigninResponse_RefreshTokenIsAlwaysIssuedWhenRollingTokensAreEnabled()
{
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Protect(It.IsAny<AuthenticationTicket>()))
.Returns("8xLOxBtZp8");
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(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(manager);
builder.UseRollingTokens();
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.NotNull(response.RefreshToken);
}
[Fact]
public async Task ProcessSigninResponse_RefreshTokenIsNotIssuedWhenRollingTokensAreDisabled()
{
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Protect(It.IsAny<AuthenticationTicket>()))
.Returns("8xLOxBtZp8");
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(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
});
var server = CreateAuthorizationServer(builder =>
{
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.Null(response.RefreshToken);
}
[Fact]
public async Task ProcessSigninResponse_AuthorizationCodeIsAutomaticallyRedeemed()
{
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetPresenters("Fabrikam");
ticket.SetTokenId("3E228451-1555-46F7-A471-951EFBA23A56");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.AuthorizationCode);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("SplxlOBeZQQYbYS6WxSbIA"))
.Returns(ticket);
var token = new OpenIddictToken();
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()))
.ReturnsAsync(token);
instance.Setup(mock => mock.IsValidAsync(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.AuthorizationCodeFormat = format.Object);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(TokenEndpoint, new OpenIdConnectRequest
{
ClientId = "Fabrikam",
Code = "SplxlOBeZQQYbYS6WxSbIA",
GrantType = OpenIdConnectConstants.GrantTypes.AuthorizationCode,
RedirectUri = "http://www.fabrikam.com/path"
});
// Assert
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("3E228451-1555-46F7-A471-951EFBA23A56", It.IsAny<CancellationToken>()), Times.Exactly(2));
Mock.Get(manager).Verify(mock => mock.RedeemAsync(token, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ProcessSigninResponse_RefreshTokenIsAutomaticallyRedeemedWhenRollingTokensAreEnabled()
{
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess);
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(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(manager);
builder.UseRollingTokens();
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
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()), Times.Exactly(2));
Mock.Get(manager).Verify(mock => mock.RedeemAsync(token, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ProcessSigninResponse_RefreshTokenIsNotRedeemedWhenRollingTokensAreDisabled()
{
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess);
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(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
});
var server = CreateAuthorizationServer(builder =>
{
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
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()), Times.Exactly(2));
Mock.Get(manager).Verify(mock => mock.RedeemAsync(token, It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task ProcessSigninResponse_PreviousTokensAreAutomaticallyRevokedWhenRollingTokensAreEnabled()
{
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess);
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, "18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
var tokens = new[]
{
new OpenIddictToken(),
new OpenIddictToken(),
new OpenIddictToken()
};
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()))
.ReturnsAsync(tokens[0]);
instance.Setup(mock => mock.IsRedeemedAsync(tokens[0], It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.IsValidAsync(tokens[0], It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(tokens);
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(manager);
builder.UseRollingTokens();
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
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()), Times.Exactly(2));
Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[1], It.IsAny<CancellationToken>()), Times.Once());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[2], It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ProcessSigninResponse_PreviousTokensAreNotRevokedWhenRollingTokensAreDisabled()
{
// Arrange
var identity = new ClaimsIdentity(OpenIdConnectServerDefaults.AuthenticationScheme);
identity.AddClaim(OpenIdConnectConstants.Claims.Subject, "Bob le Bricoleur");
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(identity),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess);
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, "18D15F73-BE2B-6867-DC01-B3C1E8AFDED0");
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
var tokens = new[]
{
new OpenIddictToken(),
new OpenIddictToken(),
new OpenIddictToken()
};
var manager = CreateTokenManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()))
.ReturnsAsync(tokens[0]);
instance.Setup(mock => mock.IsRedeemedAsync(tokens[0], It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
instance.Setup(mock => mock.IsValidAsync(tokens[0], It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
instance.Setup(mock => mock.FindByAuthorizationIdAsync("18D15F73-BE2B-6867-DC01-B3C1E8AFDED0", It.IsAny<CancellationToken>()))
.ReturnsAsync(tokens);
});
var server = CreateAuthorizationServer(builder =>
{
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
Mock.Get(manager).Verify(mock => mock.FindByIdAsync("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()), Times.Exactly(2));
Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[1], It.IsAny<CancellationToken>()), Times.Never());
Mock.Get(manager).Verify(mock => mock.RevokeAsync(tokens[2], It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task ProcessSigninResponse_ExtendsLifetimeWhenRollingTokensAreDisabledAndSlidingExpirationEnabled()
{
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Protect(It.IsAny<AuthenticationTicket>()))
.Returns("8xLOxBtZp8");
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(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(manager);
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);
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.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.Once());
}
[Fact]
public async Task ProcessSigninResponse_DoesNotExtendLifetimeWhenSlidingExpirationIsDisabled()
{
// Arrange
var ticket = new AuthenticationTicket(
new ClaimsPrincipal(),
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
ticket.SetTokenId("60FFF7EA-F98E-437B-937E-5073CC313103");
ticket.SetTokenUsage(OpenIdConnectConstants.TokenUsages.RefreshToken);
ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId, OpenIdConnectConstants.Scopes.OfflineAccess);
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Protect(It.IsAny<AuthenticationTicket>()))
.Returns("8xLOxBtZp8");
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(false);
instance.Setup(mock => mock.IsValidAsync(token, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
});
var server = CreateAuthorizationServer(builder =>
{
builder.Services.AddSingleton(manager);
builder.DisableSlidingExpiration();
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);
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.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]
public async Task ProcessSigninResponse_AdHocAuthorizationIsAutomaticallyCreated()
{
// Arrange
var token = new OpenIddictToken();
var manager = CreateAuthorizationManager(instance =>
{
instance.Setup(mock => mock.FindByIdAsync("1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70", It.IsAny<CancellationToken>()))
.ReturnsAsync(new OpenIddictAuthorization());
});
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.ValidateRedirectUriAsync(application, "http://www.fabrikam.com/path", It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
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 =>
{
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");
}));
builder.Services.AddSingleton(manager);
});
var client = new OpenIdConnectClient(server.CreateClient());
// Act
var response = await client.PostAsync(AuthorizationEndpoint, new OpenIdConnectRequest
{
ClientId = "Fabrikam",
RedirectUri = "http://www.fabrikam.com/path",
ResponseType = OpenIdConnectConstants.ResponseTypes.Code,
});
// Assert
Assert.NotNull(response.Code);
Mock.Get(manager).Verify(mock => mock.CreateAsync(
It.Is<OpenIddictAuthorizationDescriptor>(descriptor =>
descriptor.ApplicationId == "3E228451-1555-46F7-A471-951EFBA23A56" &&
descriptor.Subject == "Bob le Magnifique" &&
descriptor.Type == OpenIddictConstants.AuthorizationTypes.AdHoc),
It.IsAny<CancellationToken>()), Times.Once());
}
}
}

28
test/OpenIddict.Tests/OpenIddictProviderTests.cs

@ -160,6 +160,14 @@ namespace OpenIddict.Tests
ticket.SetProperty(OpenIddictConstants.Properties.AuthorizationId, "1AF06AB2-A0FC-4E3D-86AF-E04DA8C7BE70");
}
if (request.HasParameter("do-not-flow-original-properties"))
{
var properties = new AuthenticationProperties();
properties.SetProperty("custom_property_in_new_ticket", "new_value");
return context.Authentication.SignInAsync(ticket.AuthenticationScheme, ticket.Principal, properties);
}
return context.Authentication.SignInAsync(ticket.AuthenticationScheme, ticket.Principal, ticket.Properties);
}
@ -186,46 +194,50 @@ namespace OpenIddict.Tests
return new TestServer(builder);
}
private static OpenIddictApplicationManager<OpenIddictApplication> CreateApplicationManager(Action<Mock<OpenIddictApplicationManager<OpenIddictApplication>>> setup = null)
private static OpenIddictApplicationManager<OpenIddictApplication> CreateApplicationManager(
Action<Mock<OpenIddictApplicationManager<OpenIddictApplication>>> configuration = null)
{
var manager = new Mock<OpenIddictApplicationManager<OpenIddictApplication>>(
Mock.Of<IOpenIddictApplicationStore<OpenIddictApplication>>(),
Mock.Of<ILogger<OpenIddictApplicationManager<OpenIddictApplication>>>());
setup?.Invoke(manager);
configuration?.Invoke(manager);
return manager.Object;
}
private static OpenIddictAuthorizationManager<OpenIddictAuthorization> CreateAuthorizationManager(Action<Mock<OpenIddictAuthorizationManager<OpenIddictAuthorization>>> setup = null)
private static OpenIddictAuthorizationManager<OpenIddictAuthorization> CreateAuthorizationManager(
Action<Mock<OpenIddictAuthorizationManager<OpenIddictAuthorization>>> configuration = null)
{
var manager = new Mock<OpenIddictAuthorizationManager<OpenIddictAuthorization>>(
Mock.Of<IOpenIddictAuthorizationStore<OpenIddictAuthorization>>(),
Mock.Of<ILogger<OpenIddictAuthorizationManager<OpenIddictAuthorization>>>());
setup?.Invoke(manager);
configuration?.Invoke(manager);
return manager.Object;
}
private static OpenIddictScopeManager<OpenIddictScope> CreateScopeManager(Action<Mock<OpenIddictScopeManager<OpenIddictScope>>> setup = null)
private static OpenIddictScopeManager<OpenIddictScope> CreateScopeManager(
Action<Mock<OpenIddictScopeManager<OpenIddictScope>>> configuration = null)
{
var manager = new Mock<OpenIddictScopeManager<OpenIddictScope>>(
Mock.Of<IOpenIddictScopeStore<OpenIddictScope>>(),
Mock.Of<ILogger<OpenIddictScopeManager<OpenIddictScope>>>());
setup?.Invoke(manager);
configuration?.Invoke(manager);
return manager.Object;
}
private static OpenIddictTokenManager<OpenIddictToken> CreateTokenManager(Action<Mock<OpenIddictTokenManager<OpenIddictToken>>> setup = null)
private static OpenIddictTokenManager<OpenIddictToken> CreateTokenManager(
Action<Mock<OpenIddictTokenManager<OpenIddictToken>>> configuration = null)
{
var manager = new Mock<OpenIddictTokenManager<OpenIddictToken>>(
Mock.Of<IOpenIddictTokenStore<OpenIddictToken>>(),
Mock.Of<ILogger<OpenIddictTokenManager<OpenIddictToken>>>());
setup?.Invoke(manager);
configuration?.Invoke(manager);
return manager.Object;
}

Loading…
Cancel
Save