From fcc0ddd99cdcebdae22d71008d83643b69c8d08b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Fri, 4 Oct 2024 17:05:14 +0200 Subject: [PATCH] Introduce new RevokeByApplicationIdAsync()/RevokeBySubjectAsync() APIs in the authorization/token managers/stores --- .../IOpenIddictAuthorizationManager.cs | 16 +++ .../Managers/IOpenIddictTokenManager.cs | 16 +++ .../Stores/IOpenIddictAuthorizationStore.cs | 16 +++ .../Stores/IOpenIddictTokenStore.cs | 16 +++ .../OpenIddictAuthorizationManager.cs | 40 ++++++ .../Managers/OpenIddictTokenManager.cs | 40 ++++++ ...IddictEntityFrameworkAuthorizationStore.cs | 92 ++++++++++++++ .../OpenIddictEntityFrameworkTokenStore.cs | 92 ++++++++++++++ ...ctEntityFrameworkCoreAuthorizationStore.cs | 118 ++++++++++++++++++ ...OpenIddictEntityFrameworkCoreTokenStore.cs | 118 ++++++++++++++++++ .../OpenIddictMongoDbAuthorizationStore.cs | 36 ++++++ .../Stores/OpenIddictMongoDbTokenStore.cs | 36 ++++++ 12 files changed, 636 insertions(+) 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) {