Browse Source

Backport the timestamp changes to OpenIddict 1.x

pull/553/head
Kévin Chalet 9 years ago
parent
commit
9f0a04ee95
  1. 53
      src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs
  2. 53
      src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
  3. 56
      src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
  4. 68
      src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
  5. 24
      src/OpenIddict.EntityFramework/OpenIddictExtensions.cs
  6. 19
      src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs
  7. 21
      src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
  8. 19
      src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs
  9. 19
      src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs
  10. 16
      src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs
  11. 18
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs
  12. 18
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
  13. 18
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs
  14. 18
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
  15. 6
      src/OpenIddict.Models/OpenIddictApplication.cs
  16. 6
      src/OpenIddict.Models/OpenIddictAuthorization.cs
  17. 6
      src/OpenIddict.Models/OpenIddictScope.cs
  18. 6
      src/OpenIddict.Models/OpenIddictToken.cs
  19. 8
      src/OpenIddict/OpenIddictProvider.Exchange.cs
  20. 130
      src/OpenIddict/OpenIddictProvider.Helpers.cs
  21. 18
      src/OpenIddict/OpenIddictProvider.Revocation.cs
  22. 49
      src/OpenIddict/OpenIddictProvider.Signin.cs
  23. 222
      test/OpenIddict.Tests/OpenIddictProviderTests.Signin.cs

53
src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs

@ -136,7 +136,18 @@ namespace OpenIddict.Core
}
await ValidateAsync(application, cancellationToken);
return await Store.CreateAsync(application, cancellationToken);
try
{
return await Store.CreateAsync(application, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to create a new application.");
throw;
}
}
/// <summary>
@ -182,7 +193,18 @@ namespace OpenIddict.Core
}
await ValidateAsync(descriptor, cancellationToken);
return await Store.CreateAsync(descriptor, cancellationToken);
try
{
return await Store.CreateAsync(descriptor, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to create a new application.");
throw;
}
}
/// <summary>
@ -193,14 +215,24 @@ namespace OpenIddict.Core
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public virtual async Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
throw new ArgumentNullException(nameof(application));
}
return Store.DeleteAsync(application, cancellationToken);
try
{
await Store.DeleteAsync(application, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to delete an existing application.");
throw;
}
}
/// <summary>
@ -513,7 +545,18 @@ namespace OpenIddict.Core
}
await ValidateAsync(application, cancellationToken);
await Store.UpdateAsync(application, cancellationToken);
try
{
await Store.UpdateAsync(application, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to update an existing application.");
throw;
}
}
/// <summary>

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

@ -93,7 +93,18 @@ namespace OpenIddict.Core
}
await ValidateAsync(authorization, cancellationToken);
return await Store.CreateAsync(authorization, cancellationToken);
try
{
return await Store.CreateAsync(authorization, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to create a new authorization.");
throw;
}
}
/// <summary>
@ -119,7 +130,18 @@ namespace OpenIddict.Core
}
await ValidateAsync(descriptor, cancellationToken);
return await Store.CreateAsync(descriptor, cancellationToken);
try
{
return await Store.CreateAsync(descriptor, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to create a new authorization.");
throw;
}
}
/// <summary>
@ -130,14 +152,24 @@ namespace OpenIddict.Core
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public virtual async Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
throw new ArgumentNullException(nameof(authorization));
}
return Store.DeleteAsync(authorization, cancellationToken);
try
{
await Store.DeleteAsync(authorization, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to delete an existing authorization.");
throw;
}
}
/// <summary>
@ -344,7 +376,18 @@ namespace OpenIddict.Core
}
await ValidateAsync(authorization, cancellationToken);
await Store.UpdateAsync(authorization, cancellationToken);
try
{
await Store.UpdateAsync(authorization, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to update an existing authorization.");
throw;
}
}
/// <summary>

56
src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs

@ -79,14 +79,24 @@ namespace OpenIddict.Core
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the scope.
/// </returns>
public virtual Task<TScope> CreateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public virtual async Task<TScope> CreateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return Store.CreateAsync(scope, cancellationToken);
try
{
return await Store.CreateAsync(scope, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to create a new scope.");
throw;
}
}
/// <summary>
@ -97,14 +107,24 @@ namespace OpenIddict.Core
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the scope.
/// </returns>
public virtual Task<TScope> CreateAsync([NotNull] OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken)
public virtual async Task<TScope> CreateAsync([NotNull] OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken)
{
if (descriptor == null)
{
throw new ArgumentNullException(nameof(descriptor));
}
return Store.CreateAsync(descriptor, cancellationToken);
try
{
return await Store.CreateAsync(descriptor, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to create a new scope.");
throw;
}
}
/// <summary>
@ -115,14 +135,24 @@ namespace OpenIddict.Core
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public virtual async Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return Store.DeleteAsync(scope, cancellationToken);
try
{
await Store.DeleteAsync(scope, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to delete an existing scope.");
throw;
}
}
/// <summary>
@ -188,14 +218,24 @@ namespace OpenIddict.Core
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task UpdateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public virtual async Task UpdateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
throw new ArgumentNullException(nameof(scope));
}
return Store.UpdateAsync(scope, cancellationToken);
try
{
await Store.UpdateAsync(scope, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to update an existing scope.");
throw;
}
}
}
}

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

@ -88,7 +88,18 @@ namespace OpenIddict.Core
}
await ValidateAsync(token, cancellationToken);
return await Store.CreateAsync(token, cancellationToken);
try
{
return await Store.CreateAsync(token, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to create a new token.");
throw;
}
}
/// <summary>
@ -107,7 +118,46 @@ namespace OpenIddict.Core
}
await ValidateAsync(descriptor, cancellationToken);
return await Store.CreateAsync(descriptor, cancellationToken);
try
{
return await Store.CreateAsync(descriptor, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to create a new token.");
throw;
}
}
/// <summary>
/// Removes an existing token.
/// </summary>
/// <param name="token">The token to delete.</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 async Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
try
{
await Store.DeleteAsync(token, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to delete an existing token.");
throw;
}
}
/// <summary>
@ -530,14 +580,24 @@ namespace OpenIddict.Core
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public virtual Task UpdateAsync([NotNull] TToken token, CancellationToken cancellationToken)
public virtual async Task UpdateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
return Store.UpdateAsync(token, cancellationToken);
try
{
await Store.UpdateAsync(token, cancellationToken);
}
catch (Exception exception)
{
Logger.LogError(0, exception, "An exception occurred while trying to update an existing token.");
throw;
}
}
/// <summary>

24
src/OpenIddict.EntityFramework/OpenIddictExtensions.cs

@ -192,6 +192,12 @@ namespace Microsoft.Extensions.DependencyInjection
.HasMaxLength(450)
.HasColumnAnnotation(IndexAnnotation.AnnotationName, new IndexAnnotation(new IndexAttribute()));
builder.Entity<TApplication>()
.Property(application => application.Timestamp)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed)
.IsConcurrencyToken()
.IsRowVersion();
builder.Entity<TApplication>()
.Property(application => application.Type)
.IsRequired();
@ -221,6 +227,12 @@ namespace Microsoft.Extensions.DependencyInjection
.Property(authorization => authorization.Subject)
.IsRequired();
builder.Entity<TAuthorization>()
.Property(authorization => authorization.Timestamp)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed)
.IsConcurrencyToken()
.IsRowVersion();
builder.Entity<TAuthorization>()
.Property(authorization => authorization.Type)
.IsRequired();
@ -241,6 +253,12 @@ namespace Microsoft.Extensions.DependencyInjection
.Property(scope => scope.Name)
.IsRequired();
builder.Entity<TScope>()
.Property(scope => scope.Timestamp)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed)
.IsConcurrencyToken()
.IsRowVersion();
builder.Entity<TScope>()
.ToTable("OpenIddictScopes");
@ -257,6 +275,12 @@ namespace Microsoft.Extensions.DependencyInjection
.Property(token => token.Subject)
.IsRequired();
builder.Entity<TToken>()
.Property(token => token.Timestamp)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed)
.IsConcurrencyToken()
.IsRowVersion();
builder.Entity<TToken>()
.Property(token => token.Type)
.IsRequired();

19
src/OpenIddict.EntityFramework/Stores/OpenIddictApplicationStore.cs

@ -7,7 +7,6 @@
using System;
using System.Collections.Immutable;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -172,7 +171,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public override Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
@ -181,12 +180,7 @@ namespace OpenIddict.EntityFramework
Applications.Remove(application);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
/// <summary>
@ -256,7 +250,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public override Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
@ -266,12 +260,7 @@ namespace OpenIddict.EntityFramework
Applications.Attach(application);
Context.Entry(application).State = EntityState.Modified;
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
}
}

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

@ -7,7 +7,6 @@
using System;
using System.Collections.Immutable;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -179,7 +178,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public override Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
@ -187,13 +186,8 @@ namespace OpenIddict.EntityFramework
}
Authorizations.Remove(authorization);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
/// <summary>
@ -263,18 +257,13 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task UpdateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public override Task UpdateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
Authorizations.Attach(authorization);
Context.Entry(authorization).State = EntityState.Modified;
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
}
}

19
src/OpenIddict.EntityFramework/Stores/OpenIddictScopeStore.cs

@ -7,7 +7,6 @@
using System;
using System.Collections.Immutable;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -141,7 +140,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public override Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
@ -150,12 +149,7 @@ namespace OpenIddict.EntityFramework
Scopes.Remove(scope);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
/// <summary>
@ -206,7 +200,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task UpdateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public override Task UpdateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
@ -216,12 +210,7 @@ namespace OpenIddict.EntityFramework
Scopes.Attach(scope);
Context.Entry(scope).State = EntityState.Modified;
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
}
}

19
src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs

@ -7,7 +7,6 @@
using System;
using System.Collections.Immutable;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -193,7 +192,7 @@ namespace OpenIddict.EntityFramework
/// <param name="token">The token to delete.</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 override async Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken)
public override Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -202,12 +201,7 @@ namespace OpenIddict.EntityFramework
Tokens.Remove(token);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
/// <summary>
@ -359,7 +353,7 @@ namespace OpenIddict.EntityFramework
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task UpdateAsync([NotNull] TToken token, CancellationToken cancellationToken)
public override Task UpdateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -369,12 +363,7 @@ namespace OpenIddict.EntityFramework
Tokens.Attach(token);
Context.Entry(token).State = EntityState.Modified;
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
}
}

16
src/OpenIddict.EntityFrameworkCore/OpenIddictExtensions.cs

@ -228,6 +228,10 @@ namespace Microsoft.Extensions.DependencyInjection
entity.Property(application => application.ClientId)
.IsRequired(required: true);
entity.Property(application => application.Timestamp)
.ValueGeneratedOnAddOrUpdate()
.IsConcurrencyToken();
entity.Property(application => application.Type)
.IsRequired();
@ -255,6 +259,10 @@ namespace Microsoft.Extensions.DependencyInjection
entity.Property(authorization => authorization.Subject)
.IsRequired();
entity.Property(authorization => authorization.Timestamp)
.ValueGeneratedOnAddOrUpdate()
.IsConcurrencyToken();
entity.Property(authorization => authorization.Type)
.IsRequired();
@ -271,6 +279,10 @@ namespace Microsoft.Extensions.DependencyInjection
{
entity.HasKey(scope => scope.Id);
entity.Property(scope => scope.Timestamp)
.ValueGeneratedOnAddOrUpdate()
.IsConcurrencyToken();
entity.Property(scope => scope.Name)
.IsRequired();
@ -288,6 +300,10 @@ namespace Microsoft.Extensions.DependencyInjection
entity.Property(token => token.Subject)
.IsRequired();
entity.Property(token => token.Timestamp)
.ValueGeneratedOnAddOrUpdate()
.IsConcurrencyToken();
entity.Property(token => token.Type)
.IsRequired();

18
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictApplicationStore.cs

@ -171,7 +171,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public override Task DeleteAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
@ -180,12 +180,7 @@ namespace OpenIddict.EntityFrameworkCore
Context.Remove(application);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
/// <summary>
@ -236,7 +231,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
public override Task UpdateAsync([NotNull] TApplication application, CancellationToken cancellationToken)
{
if (application == null)
{
@ -246,12 +241,7 @@ namespace OpenIddict.EntityFrameworkCore
Context.Attach(application);
Context.Update(application);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
}
}

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

@ -180,7 +180,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public override Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
if (authorization == null)
{
@ -189,12 +189,7 @@ namespace OpenIddict.EntityFrameworkCore
Context.Remove(authorization);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
/// <summary>
@ -245,18 +240,13 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task UpdateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
public override Task UpdateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken)
{
Context.Attach(authorization);
Context.Update(authorization);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
}
}

18
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs

@ -140,7 +140,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public override Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
@ -149,12 +149,7 @@ namespace OpenIddict.EntityFrameworkCore
Context.Remove(scope);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
/// <summary>
@ -205,7 +200,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task UpdateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
public override Task UpdateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
{
if (scope == null)
{
@ -215,12 +210,7 @@ namespace OpenIddict.EntityFrameworkCore
Context.Attach(scope);
Context.Entry(scope).State = EntityState.Modified;
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
}
}

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

@ -196,7 +196,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <param name="token">The token to delete.</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 override async Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken)
public override Task DeleteAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -205,12 +205,7 @@ namespace OpenIddict.EntityFrameworkCore
Context.Remove(token);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
/// <summary>
@ -347,7 +342,7 @@ namespace OpenIddict.EntityFrameworkCore
/// <returns>
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
/// </returns>
public override async Task UpdateAsync([NotNull] TToken token, CancellationToken cancellationToken)
public override Task UpdateAsync([NotNull] TToken token, CancellationToken cancellationToken)
{
if (token == null)
{
@ -357,12 +352,7 @@ namespace OpenIddict.EntityFrameworkCore
Context.Attach(token);
Context.Update(token);
try
{
await Context.SaveChangesAsync(cancellationToken);
}
catch (DbUpdateConcurrencyException) { }
return Context.SaveChangesAsync(cancellationToken);
}
}
}

6
src/OpenIddict.Models/OpenIddictApplication.cs

@ -77,6 +77,12 @@ namespace OpenIddict.Models
/// </summary>
public virtual string RedirectUris { get; set; }
/// <summary>
/// Gets or sets the timestamp associated with the current
/// application, which is used as a concurrency token.
/// </summary>
public virtual byte[] Timestamp { get; set; }
/// <summary>
/// Gets the list of the tokens associated with this application.
/// </summary>

6
src/OpenIddict.Models/OpenIddictAuthorization.cs

@ -60,6 +60,12 @@ namespace OpenIddict.Models
/// </summary>
public virtual string Subject { get; set; }
/// <summary>
/// Gets or sets the timestamp associated with the current
/// authorization, which is used as a concurrency token.
/// </summary>
public virtual byte[] Timestamp { get; set; }
/// <summary>
/// Gets or sets the list of tokens
/// associated with the current authorization.

6
src/OpenIddict.Models/OpenIddictScope.cs

@ -25,6 +25,12 @@ namespace OpenIddict.Models
/// </summary>
public class OpenIddictScope<TKey> where TKey : IEquatable<TKey>
{
/// <summary>
/// Gets or sets the timestamp associated with the
/// current scope, which is used as a concurrency token.
/// </summary>
public virtual byte[] Timestamp { get; set; }
/// <summary>
/// Gets or sets the public description
/// associated with the current scope.

6
src/OpenIddict.Models/OpenIddictToken.cs

@ -85,6 +85,12 @@ namespace OpenIddict.Models
/// </summary>
public virtual string Subject { get; set; }
/// <summary>
/// Gets or sets the timestamp associated with the
/// current token, which is used as a concurrency token.
/// </summary>
public virtual byte[] Timestamp { get; set; }
/// <summary>
/// Gets or sets the type of the current token.
/// </summary>

8
src/OpenIddict/OpenIddictProvider.Exchange.cs

@ -253,8 +253,12 @@ namespace OpenIddict
// See https://tools.ietf.org/html/rfc6749#section-10.5 for more information.
if (await tokens.IsRedeemedAsync(token, context.HttpContext.RequestAborted))
{
await RevokeAuthorizationAsync(context.Ticket, context.HttpContext);
await RevokeTokensAsync(context.Ticket, context.HttpContext);
// Try to revoke the authorization and the associated tokens.
// If the operation fails, the helpers will automatically log
// and swallow the exception to ensure that a valid error
// response will be returned to the client application.
await TryRevokeAuthorizationAsync(context.Ticket, context.HttpContext);
await TryRevokeTokensAsync(context.Ticket, context.HttpContext);
logger.LogError("The token request was rejected because the authorization code " +
"or refresh token '{Identifier}' has already been redeemed.", identifier);

130
src/OpenIddict/OpenIddictProvider.Helpers.cs

@ -314,37 +314,54 @@ namespace OpenIddict
return ticket;
}
private async Task RevokeAuthorizationAsync([NotNull] AuthenticationTicket ticket, [NotNull] HttpContext context)
private async Task<bool> TryRevokeAuthorizationAsync([NotNull] AuthenticationTicket ticket, [NotNull] HttpContext context)
{
var authorizations = context.RequestServices.GetRequiredService<OpenIddictAuthorizationManager<TAuthorization>>();
var logger = context.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
// Note: if the authorization identifier or the authorization itself
// cannot be found, return true as the authorization doesn't need
// to be revoked if it doesn't exist or is already invalid.
var identifier = ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId);
if (string.IsNullOrEmpty(identifier))
{
return;
return true;
}
var authorization = await authorizations.FindByIdAsync(identifier, context.RequestAborted);
if (authorization == null)
{
return;
return true;
}
await authorizations.RevokeAsync(authorization, context.RequestAborted);
try
{
await authorizations.RevokeAsync(authorization, context.RequestAborted);
logger.LogInformation("The authorization '{Identifier}' was automatically revoked.", identifier);
return true;
}
catch (Exception exception)
{
logger.LogWarning(0, exception, "An exception occurred while trying to revoke the authorization " +
"associated with the token '{Identifier}'.", identifier);
logger.LogInformation("The authorization '{Identifier}' was automatically revoked.", identifier);
return false;
}
}
private async Task RevokeTokensAsync([NotNull] AuthenticationTicket ticket, [NotNull] HttpContext context)
private async Task<bool> TryRevokeTokensAsync([NotNull] AuthenticationTicket ticket, [NotNull] HttpContext context)
{
var logger = context.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var tokens = context.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
// Note: if the authorization identifier is null, return true as no tokens need to be revoked.
var identifier = ticket.GetProperty(OpenIddictConstants.Properties.AuthorizationId);
if (string.IsNullOrEmpty(identifier))
{
return;
return true;
}
foreach (var token in await tokens.FindByAuthorizationIdAsync(identifier, context.RequestAborted))
@ -355,10 +372,103 @@ namespace OpenIddict
continue;
}
await tokens.RevokeAsync(token, context.RequestAborted);
try
{
await tokens.RevokeAsync(token, context.RequestAborted);
logger.LogInformation("The token '{Identifier}' was automatically revoked.",
await tokens.GetIdAsync(token, context.RequestAborted));
}
catch (Exception exception)
{
logger.LogWarning(0, exception, "An exception occurred while trying to revoke the tokens " +
"associated with the token '{Identifier}'.",
await tokens.GetIdAsync(token, context.RequestAborted));
return false;
}
}
return true;
}
private async Task<bool> TryRedeemTokenAsync([NotNull] AuthenticationTicket ticket, [NotNull] HttpContext context)
{
var logger = context.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var tokens = context.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
// Note: if the token identifier or the token itself
// cannot be found, return true as the token doesn't need
// to be revoked if it doesn't exist or is already invalid.
var identifier = ticket.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
return true;
}
var token = await tokens.FindByIdAsync(identifier, context.RequestAborted);
if (token == null)
{
return true;
}
try
{
await tokens.RedeemAsync(token, context.RequestAborted);
logger.LogInformation("The token '{Identifier}' was automatically marked as redeemed.", identifier);
return true;
}
catch (Exception exception)
{
logger.LogWarning(0, exception, "An exception occurred while trying to " +
"redeem the token '{Identifier}'.", identifier);
return false;
}
}
private async Task<bool> TryExtendTokenAsync(
[NotNull] AuthenticationTicket ticket, [NotNull] HttpContext context, [NotNull] OpenIddictOptions options)
{
var logger = context.RequestServices.GetRequiredService<ILogger<OpenIddictProvider<TApplication, TAuthorization, TScope, TToken>>>();
var tokens = context.RequestServices.GetRequiredService<OpenIddictTokenManager<TToken>>();
var identifier = ticket.GetTokenId();
if (string.IsNullOrEmpty(identifier))
{
return false;
}
var token = await tokens.FindByIdAsync(identifier, context.RequestAborted);
if (token == null)
{
return false;
}
try
{
// Compute the new expiration date of the refresh token.
var date = options.SystemClock.UtcNow;
date += ticket.GetRefreshTokenLifetime() ?? options.RefreshTokenLifetime;
await tokens.ExtendAsync(token, date, context.RequestAborted);
logger.LogInformation("The expiration date of the refresh token '{Identifier}' " +
"was automatically updated: {Date}.", identifier, date);
return true;
}
catch (Exception exception)
{
logger.LogWarning(0, exception, "An exception occurred while trying to update the " +
"expiration date of the token '{Identifier}'.", identifier);
logger.LogInformation("The token '{Identifier}' was automatically revoked.",
await tokens.GetIdAsync(token, context.RequestAborted));
return false;
}
}
}

18
src/OpenIddict/OpenIddictProvider.Revocation.cs

@ -4,6 +4,7 @@
* the license and the contributors participating to this project.
*/
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using AspNet.Security.OpenIdConnect.Extensions;
@ -198,8 +199,21 @@ namespace OpenIddict
return;
}
// Revoke the token.
await tokens.RevokeAsync(token, context.HttpContext.RequestAborted);
// Try to revoke the token. If an exception is thrown,
// log and swallow it to ensure that a valid response
// will be returned to the client application.
try
{
await tokens.RevokeAsync(token, context.HttpContext.RequestAborted);
}
catch (Exception exception)
{
logger.LogWarning(0, exception, "An exception occurred while trying to revoke the authorization " +
"associated with the token '{Identifier}'.", identifier);
return;
}
logger.LogInformation("The token '{Identifier}' was successfully revoked.", identifier);

49
src/OpenIddict/OpenIddictProvider.Signin.cs

@ -11,8 +11,6 @@ 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
@ -24,9 +22,6 @@ namespace OpenIddict
{
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()))
{
@ -81,22 +76,18 @@ namespace OpenIddict
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)
if (!await TryRedeemTokenAsync(context.Ticket, context.HttpContext))
{
await tokens.RedeemAsync(token, context.HttpContext.RequestAborted);
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The specified authorization code is no longer valid.");
logger.LogInformation("The token '{Identifier}' was automatically marked as redeemed.", identifier);
return;
}
}
@ -104,7 +95,14 @@ namespace OpenIddict
// 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);
if (!await TryRevokeTokensAsync(context.Ticket, context.HttpContext))
{
context.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The specified refresh token is no longer valid.");
return;
}
}
// When rolling tokens are disabled, extend the expiration date
@ -112,24 +110,17 @@ namespace OpenIddict
// 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)
if (!await TryExtendTokenAsync(context.Ticket, context.HttpContext, options))
{
// 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.Reject(
error: OpenIdConnectConstants.Errors.InvalidGrant,
description: "The specified refresh token is no longer valid.");
context.IncludeRefreshToken = false;
return;
}
// If the refresh token entry could not be
// found in the database, generate a new one.
// Prevent the OpenID Connect server from returning a new refresh token.
context.IncludeRefreshToken = false;
}
}

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

@ -277,6 +277,78 @@ namespace OpenIddict.Tests
Mock.Get(manager).Verify(mock => mock.RedeemAsync(token, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ProcessSigninResponse_ReturnsErrorResponseWhenRedeemingAuthorizationCodeFails()
{
// 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);
instance.Setup(mock => mock.RedeemAsync(token, It.IsAny<CancellationToken>()))
.ThrowsAsync(new Exception());
});
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
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.Exactly(2));
Mock.Get(manager).Verify(mock => mock.RedeemAsync(token, It.IsAny<CancellationToken>()), Times.Once());
}
[Fact]
public async Task ProcessSigninResponse_RefreshTokenIsAutomaticallyRedeemedWhenRollingTokensAreEnabled()
{
@ -295,6 +367,9 @@ namespace OpenIddict.Tests
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Protect(It.IsAny<AuthenticationTicket>()))
.Returns("8xLOxBtZp8");
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
@ -331,6 +406,75 @@ namespace OpenIddict.Tests
});
// Assert
Assert.NotNull(response.RefreshToken);
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_ReturnsErrorResponseWhenRedeemingRefreshTokenFails()
{
// 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);
instance.Setup(mock => mock.RedeemAsync(token, It.IsAny<CancellationToken>()))
.ThrowsAsync(new Exception());
});
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.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("60FFF7EA-F98E-437B-937E-5073CC313103", It.IsAny<CancellationToken>()), Times.Exactly(2));
Mock.Get(manager).Verify(mock => mock.RedeemAsync(token, It.IsAny<CancellationToken>()), Times.Once());
}
@ -387,6 +531,8 @@ namespace OpenIddict.Tests
});
// Assert
Assert.Null(response.RefreshToken);
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());
}
@ -410,6 +556,9 @@ namespace OpenIddict.Tests
var format = new Mock<ISecureDataFormat<AuthenticationTicket>>();
format.Setup(mock => mock.Protect(It.IsAny<AuthenticationTicket>()))
.Returns("8xLOxBtZp8");
format.Setup(mock => mock.Unprotect("8xLOxBtZp8"))
.Returns(ticket);
@ -452,6 +601,8 @@ namespace OpenIddict.Tests
});
// Assert
Assert.NotNull(response.RefreshToken);
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());
@ -516,6 +667,8 @@ namespace OpenIddict.Tests
});
// Assert
Assert.Null(response.RefreshToken);
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());
@ -653,6 +806,75 @@ namespace OpenIddict.Tests
It.IsAny<CancellationToken>()), Times.Never());
}
[Fact]
public async Task ProcessSigninResponse_ReturnsErrorResponseWhenExtendingLifetimeOfExistingTokenFailed()
{
// 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);
instance.Setup(mock => mock.ExtendAsync(token, It.IsAny<DateTimeOffset?>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new Exception());
});
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.Equal(OpenIdConnectConstants.Errors.InvalidGrant, response.Error);
Assert.Equal("The specified refresh token is no longer valid.", response.ErrorDescription);
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_AdHocAuthorizationIsAutomaticallyCreated()
{

Loading…
Cancel
Save