diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs
index 23bacf25..6b9e61c1 100644
--- a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs
+++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs
@@ -394,6 +394,22 @@ public interface IOpenIddictAuthorizationManager
/// The number of authorizations that were removed.
ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default);
+ ///
+ /// Revokes all the authorizations associated with the specified application identifier.
+ ///
+ /// The application identifier associated with the authorizations.
+ /// The that can be used to abort the operation.
+ /// The number of authorizations associated with the specified application that were marked as revoked.
+ ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken = default);
+
+ ///
+ /// Revokes all the authorizations associated with the specified subject.
+ ///
+ /// The subject associated with the authorizations.
+ /// The that can be used to abort the operation.
+ /// The number of authorizations associated with the specified subject that were marked as revoked.
+ ValueTask RevokeBySubjectAsync(string subject, CancellationToken cancellationToken = default);
+
///
/// Tries to revoke an authorization.
///
diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs
index 1866a21c..f8e4c0ba 100644
--- a/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs
+++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs
@@ -409,6 +409,14 @@ public interface IOpenIddictTokenManager
/// The number of tokens that were removed.
ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default);
+ ///
+ /// Revokes all the tokens associated with the specified application identifier.
+ ///
+ /// The application identifier associated with the tokens.
+ /// The that can be used to abort the operation.
+ /// The number of tokens associated with the specified application that were marked as revoked.
+ ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken = default);
+
///
/// Revokes all the tokens associated with the specified authorization identifier.
///
@@ -417,6 +425,14 @@ public interface IOpenIddictTokenManager
/// The number of tokens associated with the specified authorization that were marked as revoked.
ValueTask RevokeByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken = default);
+ ///
+ /// Revokes all the tokens associated with the specified subject.
+ ///
+ /// The subject associated with the tokens.
+ /// The that can be used to abort the operation.
+ /// The number of tokens associated with the specified subject that were marked as revoked.
+ ValueTask RevokeBySubjectAsync(string subject, CancellationToken cancellationToken = default);
+
///
/// Tries to redeem a token.
///
diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs
index bb5e122b..01121dac 100644
--- a/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs
+++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs
@@ -279,6 +279,22 @@ public interface IOpenIddictAuthorizationStore where TAuthorizat
/// The number of authorizations that were removed.
ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken);
+ ///
+ /// Revokes all the authorizations associated with the specified application identifier.
+ ///
+ /// The application identifier associated with the authorizations.
+ /// The that can be used to abort the operation.
+ /// The number of authorizations associated with the specified application that were marked as revoked.
+ ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken = default);
+
+ ///
+ /// Revokes all the authorizations associated with the specified subject.
+ ///
+ /// The subject associated with the authorizations.
+ /// The that can be used to abort the operation.
+ /// The number of authorizations associated with the specified subject that were marked as revoked.
+ ValueTask RevokeBySubjectAsync(string subject, CancellationToken cancellationToken = default);
+
///
/// Sets the application identifier associated with an authorization.
///
diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs
index dfb6af73..af65b61f 100644
--- a/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs
+++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs
@@ -326,6 +326,14 @@ public interface IOpenIddictTokenStore where TToken : class
/// The number of tokens that were removed.
ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken);
+ ///
+ /// Revokes all the tokens associated with the specified application identifier.
+ ///
+ /// The application identifier associated with the tokens.
+ /// The that can be used to abort the operation.
+ /// The number of tokens associated with the specified application that were marked as revoked.
+ ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken = default);
+
///
/// Revokes all the tokens associated with the specified authorization identifier.
///
@@ -334,6 +342,14 @@ public interface IOpenIddictTokenStore where TToken : class
/// The number of tokens associated with the specified authorization that were marked as revoked.
ValueTask RevokeByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken);
+ ///
+ /// Revokes all the tokens associated with the specified subject.
+ ///
+ /// The subject associated with the tokens.
+ /// The that can be used to abort the operation.
+ /// The number of tokens associated with the specified subject that were marked as revoked.
+ ValueTask RevokeBySubjectAsync(string subject, CancellationToken cancellationToken = default);
+
///
/// Sets the application identifier associated with a token.
///
diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
index 0ef78171..54f3aad9 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs
@@ -1028,6 +1028,38 @@ public class OpenIddictAuthorizationManager : IOpenIddictAuthori
public virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default)
=> Store.PruneAsync(threshold, cancellationToken);
+ ///
+ /// Revokes all the authorizations associated with the specified application identifier.
+ ///
+ /// The application identifier associated with the authorizations.
+ /// The that can be used to abort the operation.
+ /// The number of authorizations associated with the specified application that were marked as revoked.
+ public virtual ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
+ }
+
+ return Store.RevokeByApplicationIdAsync(identifier, cancellationToken);
+ }
+
+ ///
+ /// Revokes all the authorizations associated with the specified subject.
+ ///
+ /// The subject associated with the authorizations.
+ /// The that can be used to abort the operation.
+ /// The number of authorizations associated with the specified subject that were marked as revoked.
+ public virtual ValueTask RevokeBySubjectAsync(string subject, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrEmpty(subject))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(subject));
+ }
+
+ return Store.RevokeBySubjectAsync(subject, cancellationToken);
+ }
+
///
/// Tries to revoke an authorization.
///
@@ -1337,6 +1369,14 @@ public class OpenIddictAuthorizationManager : IOpenIddictAuthori
ValueTask IOpenIddictAuthorizationManager.PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
=> PruneAsync(threshold, cancellationToken);
+ ///
+ ValueTask IOpenIddictAuthorizationManager.RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
+ => RevokeByApplicationIdAsync(identifier, cancellationToken);
+
+ ///
+ ValueTask IOpenIddictAuthorizationManager.RevokeBySubjectAsync(string subject, CancellationToken cancellationToken)
+ => RevokeBySubjectAsync(subject, cancellationToken);
+
///
ValueTask IOpenIddictAuthorizationManager.TryRevokeAsync(object authorization, CancellationToken cancellationToken)
=> TryRevokeAsync((TAuthorization) authorization, cancellationToken);
diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
index aba9da05..d7438298 100644
--- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
+++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs
@@ -1055,6 +1055,22 @@ public class OpenIddictTokenManager : IOpenIddictTokenManager where TTok
public virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default)
=> Store.PruneAsync(threshold, cancellationToken);
+ ///
+ /// Revokes all the tokens associated with the specified application identifier.
+ ///
+ /// The application identifier associated with the tokens.
+ /// The that can be used to abort the operation.
+ /// The number of tokens associated with the specified application that were marked as revoked.
+ public virtual ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
+ }
+
+ return Store.RevokeByApplicationIdAsync(identifier, cancellationToken);
+ }
+
///
/// Revokes all the tokens associated with the specified authorization identifier.
///
@@ -1071,6 +1087,22 @@ public class OpenIddictTokenManager : IOpenIddictTokenManager where TTok
return Store.RevokeByAuthorizationIdAsync(identifier, cancellationToken);
}
+ ///
+ /// Revokes all the tokens associated with the specified subject.
+ ///
+ /// The subject associated with the tokens.
+ /// The that can be used to abort the operation.
+ /// The number of tokens associated with the specified subject that were marked as revoked.
+ public virtual ValueTask RevokeBySubjectAsync(string subject, CancellationToken cancellationToken = default)
+ {
+ if (string.IsNullOrEmpty(subject))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(subject));
+ }
+
+ return Store.RevokeBySubjectAsync(subject, cancellationToken);
+ }
+
///
/// Tries to redeem a token.
///
@@ -1501,10 +1533,18 @@ public class OpenIddictTokenManager : IOpenIddictTokenManager where TTok
ValueTask IOpenIddictTokenManager.PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken)
=> PruneAsync(threshold, cancellationToken);
+ ///
+ ValueTask IOpenIddictTokenManager.RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
+ => RevokeByApplicationIdAsync(identifier, cancellationToken);
+
///
ValueTask IOpenIddictTokenManager.RevokeByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken)
=> RevokeByAuthorizationIdAsync(identifier, cancellationToken);
+ ///
+ ValueTask IOpenIddictTokenManager.RevokeBySubjectAsync(string subject, CancellationToken cancellationToken)
+ => RevokeBySubjectAsync(subject, cancellationToken);
+
///
ValueTask IOpenIddictTokenManager.TryRedeemAsync(object token, CancellationToken cancellationToken)
=> TryRedeemAsync((TToken) token, cancellationToken);
diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs
index e427d7de..d6020f77 100644
--- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs
+++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs
@@ -658,6 +658,98 @@ public class OpenIddictEntityFrameworkAuthorizationStore
+ public virtual async ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
+ }
+
+ var key = ConvertIdentifierFromString(identifier);
+
+ List? exceptions = null;
+
+ var result = 0L;
+
+ foreach (var authorization in await (from authorization in Authorizations
+ where authorization.Application!.Id!.Equals(key)
+ select authorization).ToListAsync(cancellationToken))
+ {
+ authorization.Status = Statuses.Revoked;
+
+ try
+ {
+ await Context.SaveChangesAsync(cancellationToken);
+ }
+
+ catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
+ {
+ // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
+ Context.Entry(authorization).State = EntityState.Unchanged;
+
+ exceptions ??= [];
+ exceptions.Add(exception);
+
+ continue;
+ }
+
+ result++;
+ }
+
+ if (exceptions is not null)
+ {
+ throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
+ }
+
+ return result;
+ }
+
+ ///
+ public virtual async ValueTask RevokeBySubjectAsync(string subject, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(subject))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(subject));
+ }
+
+ List? exceptions = null;
+
+ var result = 0L;
+
+ foreach (var authorization in await (from authorization in Authorizations
+ where authorization.Subject == subject
+ select authorization).ToListAsync(cancellationToken))
+ {
+ authorization.Status = Statuses.Revoked;
+
+ try
+ {
+ await Context.SaveChangesAsync(cancellationToken);
+ }
+
+ catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
+ {
+ // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
+ Context.Entry(authorization).State = EntityState.Unchanged;
+
+ exceptions ??= [];
+ exceptions.Add(exception);
+
+ continue;
+ }
+
+ result++;
+ }
+
+ if (exceptions is not null)
+ {
+ throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
+ }
+
+ return result;
+ }
+
///
public virtual async ValueTask SetApplicationIdAsync(TAuthorization authorization,
string? identifier, CancellationToken cancellationToken)
diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs
index c446b1a9..d790bf44 100644
--- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs
+++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs
@@ -659,6 +659,53 @@ public class OpenIddictEntityFrameworkTokenStore
+ public virtual async ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
+ }
+
+ var key = ConvertIdentifierFromString(identifier);
+
+ List? exceptions = null;
+
+ var result = 0L;
+
+ foreach (var token in await (from token in Tokens
+ where token.Application!.Id!.Equals(key)
+ select token).ToListAsync(cancellationToken))
+ {
+ token.Status = Statuses.Revoked;
+
+ try
+ {
+ await Context.SaveChangesAsync(cancellationToken);
+ }
+
+ catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
+ {
+ // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
+ Context.Entry(token).State = EntityState.Unchanged;
+
+ exceptions ??= [];
+ exceptions.Add(exception);
+
+ continue;
+ }
+
+ result++;
+ }
+
+ if (exceptions is not null)
+ {
+ throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
+ }
+
+ return result;
+ }
+
///
public virtual async ValueTask RevokeByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken)
{
@@ -706,6 +753,51 @@ public class OpenIddictEntityFrameworkTokenStore
+ public virtual async ValueTask RevokeBySubjectAsync(string subject, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(subject))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(subject));
+ }
+
+ List? exceptions = null;
+
+ var result = 0L;
+
+ foreach (var token in await (from token in Tokens
+ where token.Subject == subject
+ select token).ToListAsync(cancellationToken))
+ {
+ token.Status = Statuses.Revoked;
+
+ try
+ {
+ await Context.SaveChangesAsync(cancellationToken);
+ }
+
+ catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
+ {
+ // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
+ Context.Entry(token).State = EntityState.Unchanged;
+
+ exceptions ??= [];
+ exceptions.Add(exception);
+
+ continue;
+ }
+
+ result++;
+ }
+
+ if (exceptions is not null)
+ {
+ throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
+ }
+
+ return result;
+ }
+
///
public virtual async ValueTask SetApplicationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken)
{
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs
index b2151a02..21115ab0 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs
@@ -799,6 +799,124 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore
+ public virtual async ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
+ }
+
+ var key = ConvertIdentifierFromString(identifier);
+
+#if SUPPORTS_BULK_DBSET_OPERATIONS
+ if (!Options.CurrentValue.DisableBulkOperations)
+ {
+ return await (
+ from authorization in Authorizations
+ where authorization.Application!.Id!.Equals(key)
+ select authorization).ExecuteUpdateAsync(entity => entity.SetProperty(
+ authorization => authorization.Status, Statuses.Revoked), cancellationToken);
+
+ // Note: calling DbContext.SaveChangesAsync() is not necessary
+ // with bulk update operations as they are executed immediately.
+ }
+#endif
+ List? exceptions = null;
+
+ var result = 0L;
+
+ foreach (var authorization in await (from authorization in Authorizations
+ where authorization.Application!.Id!.Equals(key)
+ select authorization).ToListAsync(cancellationToken))
+ {
+ authorization.Status = Statuses.Revoked;
+
+ try
+ {
+ await Context.SaveChangesAsync(cancellationToken);
+ }
+
+ catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
+ {
+ // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
+ Context.Entry(authorization).State = EntityState.Unchanged;
+
+ exceptions ??= [];
+ exceptions.Add(exception);
+
+ continue;
+ }
+
+ result++;
+ }
+
+ if (exceptions is not null)
+ {
+ throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
+ }
+
+ return result;
+ }
+
+ ///
+ public virtual async ValueTask RevokeBySubjectAsync(string subject, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(subject))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(subject));
+ }
+
+#if SUPPORTS_BULK_DBSET_OPERATIONS
+ if (!Options.CurrentValue.DisableBulkOperations)
+ {
+ return await (
+ from authorization in Authorizations
+ where authorization.Subject == subject
+ select authorization).ExecuteUpdateAsync(entity => entity.SetProperty(
+ authorization => authorization.Status, Statuses.Revoked), cancellationToken);
+
+ // Note: calling DbContext.SaveChangesAsync() is not necessary
+ // with bulk update operations as they are executed immediately.
+ }
+#endif
+ List? exceptions = null;
+
+ var result = 0L;
+
+ foreach (var authorization in await (from authorization in Authorizations
+ where authorization.Subject == subject
+ select authorization).ToListAsync(cancellationToken))
+ {
+ authorization.Status = Statuses.Revoked;
+
+ try
+ {
+ await Context.SaveChangesAsync(cancellationToken);
+ }
+
+ catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
+ {
+ // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
+ Context.Entry(authorization).State = EntityState.Unchanged;
+
+ exceptions ??= [];
+ exceptions.Add(exception);
+
+ continue;
+ }
+
+ result++;
+ }
+
+ if (exceptions is not null)
+ {
+ throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
+ }
+
+ return result;
+ }
+
///
public virtual async ValueTask SetApplicationIdAsync(TAuthorization authorization,
string? identifier, CancellationToken cancellationToken)
diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs
index defa48ff..09d499d0 100644
--- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs
+++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs
@@ -748,6 +748,66 @@ public class OpenIddictEntityFrameworkCoreTokenStore
+ public virtual async ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
+ }
+
+ var key = ConvertIdentifierFromString(identifier);
+
+#if SUPPORTS_BULK_DBSET_OPERATIONS
+ if (!Options.CurrentValue.DisableBulkOperations)
+ {
+ return await (
+ from token in Tokens
+ where token.Application!.Id!.Equals(key)
+ select token).ExecuteUpdateAsync(entity => entity.SetProperty(
+ token => token.Status, Statuses.Revoked), cancellationToken);
+
+ // Note: calling DbContext.SaveChangesAsync() is not necessary
+ // with bulk update operations as they are executed immediately.
+ }
+#endif
+ List? exceptions = null;
+
+ var result = 0L;
+
+ foreach (var token in await (from token in Tokens
+ where token.Application!.Id!.Equals(key)
+ select token).ToListAsync(cancellationToken))
+ {
+ token.Status = Statuses.Revoked;
+
+ try
+ {
+ await Context.SaveChangesAsync(cancellationToken);
+ }
+
+ catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
+ {
+ // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
+ Context.Entry(token).State = EntityState.Unchanged;
+
+ exceptions ??= [];
+ exceptions.Add(exception);
+
+ continue;
+ }
+
+ result++;
+ }
+
+ if (exceptions is not null)
+ {
+ throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
+ }
+
+ return result;
+ }
+
///
public virtual async ValueTask RevokeByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken)
{
@@ -808,6 +868,64 @@ public class OpenIddictEntityFrameworkCoreTokenStore
+ public virtual async ValueTask RevokeBySubjectAsync(string subject, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(subject))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(subject));
+ }
+
+#if SUPPORTS_BULK_DBSET_OPERATIONS
+ if (!Options.CurrentValue.DisableBulkOperations)
+ {
+ return await (
+ from token in Tokens
+ where token.Subject == subject
+ select token).ExecuteUpdateAsync(entity => entity.SetProperty(
+ token => token.Status, Statuses.Revoked), cancellationToken);
+
+ // Note: calling DbContext.SaveChangesAsync() is not necessary
+ // with bulk update operations as they are executed immediately.
+ }
+#endif
+ List? exceptions = null;
+
+ var result = 0L;
+
+ foreach (var token in await (from token in Tokens
+ where token.Subject == subject
+ select token).ToListAsync(cancellationToken))
+ {
+ token.Status = Statuses.Revoked;
+
+ try
+ {
+ await Context.SaveChangesAsync(cancellationToken);
+ }
+
+ catch (Exception exception) when (!OpenIddictHelpers.IsFatal(exception))
+ {
+ // Reset the state of the entity to prevents future calls to SaveChangesAsync() from failing.
+ Context.Entry(token).State = EntityState.Unchanged;
+
+ exceptions ??= [];
+ exceptions.Add(exception);
+
+ continue;
+ }
+
+ result++;
+ }
+
+ if (exceptions is not null)
+ {
+ throw new AggregateException(SR.GetResourceString(SR.ID0249), exceptions);
+ }
+
+ return result;
+ }
+
///
public virtual async ValueTask SetApplicationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken)
{
diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs
index b57e126b..d760c1ae 100644
--- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs
+++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs
@@ -549,6 +549,42 @@ public class OpenIddictMongoDbAuthorizationStore : IOpenIddictAu
return result;
}
+ ///
+ public virtual async ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
+ }
+
+ var database = await Context.GetDatabaseAsync(cancellationToken);
+ var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName);
+
+ return (await collection.UpdateManyAsync(
+ filter : authorization => authorization.ApplicationId == ObjectId.Parse(identifier),
+ update : Builders.Update.Set(authorization => authorization.Status, Statuses.Revoked),
+ options : null,
+ cancellationToken: cancellationToken)).MatchedCount;
+ }
+
+ ///
+ public virtual async ValueTask RevokeBySubjectAsync(string subject, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(subject))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(subject));
+ }
+
+ var database = await Context.GetDatabaseAsync(cancellationToken);
+ var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName);
+
+ return (await collection.UpdateManyAsync(
+ filter : authorization => authorization.Subject == subject,
+ update : Builders.Update.Set(authorization => authorization.Status, Statuses.Revoked),
+ options : null,
+ cancellationToken: cancellationToken)).MatchedCount;
+ }
+
///
public virtual ValueTask SetApplicationIdAsync(TAuthorization authorization,
string? identifier, CancellationToken cancellationToken)
diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs
index 70858b3b..797d7f78 100644
--- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs
+++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs
@@ -586,6 +586,24 @@ public class OpenIddictMongoDbTokenStore : IOpenIddictTokenStore
return result;
}
+ ///
+ public virtual async ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(identifier))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
+ }
+
+ var database = await Context.GetDatabaseAsync(cancellationToken);
+ var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName);
+
+ return (await collection.UpdateManyAsync(
+ filter : token => token.ApplicationId == ObjectId.Parse(identifier),
+ update : Builders.Update.Set(token => token.Status, Statuses.Revoked),
+ options : null,
+ cancellationToken: cancellationToken)).MatchedCount;
+ }
+
///
public virtual async ValueTask RevokeByAuthorizationIdAsync(string identifier, CancellationToken cancellationToken)
{
@@ -604,6 +622,24 @@ public class OpenIddictMongoDbTokenStore : IOpenIddictTokenStore
cancellationToken: cancellationToken)).MatchedCount;
}
+ ///
+ public virtual async ValueTask RevokeBySubjectAsync(string subject, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrEmpty(subject))
+ {
+ throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(subject));
+ }
+
+ var database = await Context.GetDatabaseAsync(cancellationToken);
+ var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName);
+
+ return (await collection.UpdateManyAsync(
+ filter : token => token.Subject == subject,
+ update : Builders.Update.Set(token => token.Status, Statuses.Revoked),
+ options : null,
+ cancellationToken: cancellationToken)).MatchedCount;
+ }
+
///
public virtual ValueTask SetApplicationIdAsync(TToken token, string? identifier, CancellationToken cancellationToken)
{