diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs
index 20864380..f6e4981b 100644
--- a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs
+++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs
@@ -357,7 +357,7 @@ namespace OpenIddict.Abstractions
Task PopulateAsync([NotNull] object authorization, [NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default);
///
- /// Removes the ad-hoc authorizations that are marked as invalid or have no valid token attached.
+ /// Removes the ad-hoc authorizations that are marked as invalid or have no valid/nonexpired token attached.
///
/// The that can be used to abort the operation.
///
diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs
index 7e6ee472..3cd9825b 100644
--- a/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs
@@ -279,7 +279,7 @@ namespace OpenIddict.Abstractions
[CanBeNull] TState state, CancellationToken cancellationToken);
///
- /// Removes the ad-hoc authorizations that are marked as invalid or have no valid token attached.
+ /// Removes the ad-hoc authorizations that are marked as invalid or have no valid/nonexpired token attached.
///
/// The that can be used to abort the operation.
///
diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
index da7312db..3644a105 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
@@ -887,7 +887,7 @@ namespace OpenIddict.Core
}
///
- /// Removes the ad-hoc authorizations that are marked as invalid or have no valid token attached.
+ /// Removes the ad-hoc authorizations that are marked as invalid or have no valid/nonexpired token attached.
///
/// The that can be used to abort the operation.
///
diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
index 73e7db65..b0478dda 100644
--- a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs
@@ -691,7 +691,7 @@ namespace OpenIddict.EntityFramework
}
///
- /// Removes the ad-hoc authorizations that are marked as invalid or have no valid token attached.
+ /// Removes the ad-hoc authorizations that are marked as invalid or have no valid/nonexpired token attached.
///
/// The that can be used to abort the operation.
///
@@ -735,7 +735,8 @@ namespace OpenIddict.EntityFramework
await (from authorization in Authorizations.Include(authorization => authorization.Tokens)
where authorization.Status != OpenIddictConstants.Statuses.Valid ||
(authorization.Type == OpenIddictConstants.AuthorizationTypes.AdHoc &&
- !authorization.Tokens.Any(token => token.Status == OpenIddictConstants.Statuses.Valid))
+ !authorization.Tokens.Any(token => token.Status == OpenIddictConstants.Statuses.Valid &&
+ token.ExpirationDate > DateTimeOffset.UtcNow))
orderby authorization.Id
select authorization).Skip(offset).Take(1_000).ToListAsync(cancellationToken);
diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs
index 33db32b3..ba2f82bd 100644
--- a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs
+++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs
@@ -810,7 +810,9 @@ namespace OpenIddict.EntityFramework
}
}
- for (var offset = 0; offset < 100_000; offset = offset + 1_000)
+ // Note: to avoid sending too many queries, the maximum number of elements
+ // that can be removed by a single call to PruneAsync() is limited to 50000.
+ for (var offset = 0; offset < 50_000; offset = offset + 1_000)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -822,8 +824,8 @@ namespace OpenIddict.EntityFramework
{
var tokens =
await (from token in Tokens
- where token.ExpirationDate < DateTimeOffset.UtcNow ||
- token.Status != OpenIddictConstants.Statuses.Valid
+ where token.Status != OpenIddictConstants.Statuses.Valid ||
+ token.ExpirationDate < DateTimeOffset.UtcNow
orderby token.Id
select token).Skip(offset).Take(1_000).ToListAsync(cancellationToken);
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
index fda4772b..9e5cab67 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs
@@ -780,7 +780,7 @@ namespace OpenIddict.EntityFrameworkCore
}
///
- /// Removes the ad-hoc authorizations that are marked as invalid or have no valid token attached.
+ /// Removes the ad-hoc authorizations that are marked as invalid or have no valid/nonexpired token attached.
///
/// The that can be used to abort the operation.
///
@@ -834,7 +834,8 @@ namespace OpenIddict.EntityFrameworkCore
await (from authorization in Authorizations.Include(authorization => authorization.Tokens).AsTracking()
where authorization.Status != OpenIddictConstants.Statuses.Valid ||
(authorization.Type == OpenIddictConstants.AuthorizationTypes.AdHoc &&
- !authorization.Tokens.Any(token => token.Status == OpenIddictConstants.Statuses.Valid))
+ !authorization.Tokens.Any(token => token.Status == OpenIddictConstants.Statuses.Valid &&
+ token.ExpirationDate > DateTimeOffset.UtcNow))
orderby authorization.Id
select authorization).Skip(offset).Take(1_000).ToListAsync(cancellationToken);
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
index dbe401a0..a58dad06 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs
@@ -935,7 +935,9 @@ namespace OpenIddict.EntityFrameworkCore
return null;
}
- for (var offset = 0; offset < 100_000; offset = offset + 1_000)
+ // Note: to avoid sending too many queries, the maximum number of elements
+ // that can be removed by a single call to PruneAsync() is limited to 50000.
+ for (var offset = 0; offset < 50_000; offset = offset + 1_000)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -947,8 +949,8 @@ namespace OpenIddict.EntityFrameworkCore
{
var tokens =
await (from token in Tokens.AsTracking()
- where token.ExpirationDate < DateTimeOffset.UtcNow ||
- token.Status != OpenIddictConstants.Statuses.Valid
+ where token.Status != OpenIddictConstants.Statuses.Valid ||
+ token.ExpirationDate < DateTimeOffset.UtcNow
orderby token.Id
select token).Skip(offset).Take(1_000).ToListAsync(cancellationToken);
diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictAuthorizationStore.cs
index 6292807d..1b9277ca 100644
--- a/src/OpenIddict.MongoDb/Stores/OpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.MongoDb/Stores/OpenIddictAuthorizationStore.cs
@@ -615,7 +615,7 @@ namespace OpenIddict.MongoDb
}
///
- /// Removes the ad-hoc authorizations that are marked as invalid or have no valid token attached.
+ /// Removes the ad-hoc authorizations that are marked as invalid or have no valid/nonexpired token attached.
///
/// The that can be used to abort the operation.
///
@@ -626,17 +626,57 @@ namespace OpenIddict.MongoDb
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName);
+ // Note: directly deleting the resulting set of an aggregate query is not supported by MongoDB.
+ // To work around this limitation, the authorization identifiers are stored in an intermediate
+ // list and delete requests are sent to remove the documents corresponding to these identifiers.
+
var identifiers =
await (from authorization in collection.AsQueryable()
join token in database.GetCollection(Options.CurrentValue.TokensCollectionName).AsQueryable()
on authorization.Id equals token.AuthorizationId into tokens
where authorization.Status != OpenIddictConstants.Statuses.Valid ||
(authorization.Type == OpenIddictConstants.AuthorizationTypes.AdHoc &&
- !tokens.Any(token => token.Status == OpenIddictConstants.Statuses.Valid))
- orderby authorization.Id
+ !tokens.Any(token => token.Status == OpenIddictConstants.Statuses.Valid &&
+ token.ExpirationDate > DateTime.UtcNow))
select authorization.Id).ToListAsync(cancellationToken);
- await collection.DeleteManyAsync(authorization => identifiers.Contains(authorization.Id));
+ // Note: to avoid generating delete requests with very large filters, a buffer is used here and the
+ // maximum number of elements that can be removed by a single call to PruneAsync() is limited to 50000.
+ foreach (var buffer in Buffer(identifiers.Take(50_000), 1_000))
+ {
+ await collection.DeleteManyAsync(authorization => buffer.Contains(authorization.Id));
+
+ // Delete the tokens associated with the pruned authorizations.
+ await database.GetCollection(Options.CurrentValue.TokensCollectionName)
+ .DeleteManyAsync(token => buffer.Contains(token.AuthorizationId), cancellationToken);
+ }
+
+ IEnumerable> Buffer(IEnumerable source, int count)
+ {
+ List buffer = null;
+
+ foreach (var element in source)
+ {
+ if (buffer == null)
+ {
+ buffer = new List();
+ }
+
+ buffer.Add(element);
+
+ if (buffer.Count == count)
+ {
+ yield return buffer;
+
+ buffer = null;
+ }
+ }
+
+ if (buffer != null)
+ {
+ yield return buffer;
+ }
+ }
}
///
diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictTokenStore.cs
index 0cd99cea..f602c80c 100644
--- a/src/OpenIddict.MongoDb/Stores/OpenIddictTokenStore.cs
+++ b/src/OpenIddict.MongoDb/Stores/OpenIddictTokenStore.cs
@@ -712,8 +712,8 @@ namespace OpenIddict.MongoDb
var database = await Context.GetDatabaseAsync(cancellationToken);
var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName);
- await collection.DeleteManyAsync(token => token.ExpirationDate < DateTimeOffset.UtcNow ||
- token.Status != OpenIddictConstants.Statuses.Valid, cancellationToken);
+ await collection.DeleteManyAsync(token => token.Status != OpenIddictConstants.Statuses.Valid ||
+ token.ExpirationDate < DateTime.UtcNow, cancellationToken);
}
///