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 23bacf250..6b9e61c1e 100644 --- a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs @@ -394,6 +394,22 @@ IAsyncEnumerable ListAsync( /// 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 1866a21ce..f8e4c0ba7 100644 --- a/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs +++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs @@ -409,6 +409,14 @@ IAsyncEnumerable ListAsync( /// 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 @@ IAsyncEnumerable ListAsync( /// 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 bb5e122bf..01121dacb 100644 --- a/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs @@ -279,6 +279,22 @@ IAsyncEnumerable ListAsync( /// 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 dfb6af732..af65b61fe 100644 --- a/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs +++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs @@ -326,6 +326,14 @@ IAsyncEnumerable ListAsync( /// 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 @@ IAsyncEnumerable ListAsync( /// 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 0ef781714..54f3aad90 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -1028,6 +1028,38 @@ public virtual async ValueTask PopulateAsync( 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 @@ ValueTask IOpenIddictAuthorizationManager.PopulateAsync(object authorization, Op 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 aba9da05b..d74382988 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -1055,6 +1055,22 @@ public virtual async ValueTask PopulateAsync( 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 virtual ValueTask RevokeByAuthorizationIdAsync(string identifier, C 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 @@ ValueTask IOpenIddictTokenManager.PopulateAsync(object token, OpenIddictTokenDes 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 e427d7ded..d6020f77b 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs @@ -658,6 +658,98 @@ orderby authorization.Id 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 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 c446b1a9d..d790bf44b 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs @@ -659,6 +659,53 @@ orderby token.Id 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 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 virtual async ValueTask RevokeByAuthorizationIdAsync(string identif 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 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 b2151a02f..21115ab02 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs @@ -799,6 +799,124 @@ orderby authorization.Id 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 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 defa48ffa..09d499d07 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreTokenStore.cs @@ -748,6 +748,66 @@ orderby token.Id 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 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 @@ from token in Tokens 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 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 b57e126b7..d760c1ae2 100644 --- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs +++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbAuthorizationStore.cs @@ -549,6 +549,42 @@ where authorization.CreationDate < threshold.UtcDateTime 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 70858b3b4..797d7f785 100644 --- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs +++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs @@ -586,6 +586,24 @@ where token.CreationDate < threshold.UtcDateTime 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 virtual async ValueTask RevokeByAuthorizationIdAsync(string identif 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) {