diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs index 6b9e61c1..7a9e41b2 100644 --- a/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictAuthorizationManager.cs @@ -394,6 +394,37 @@ public interface IOpenIddictAuthorizationManager /// The number of authorizations that were removed. ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default); + /// + /// Revokes all the authorizations corresponding to the specified + /// subject and associated with the application identifier. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The that can be used to abort the operation. + /// The number of authorizations corresponding to the criteria that were marked as revoked. + ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken = default); + + /// + /// Revokes all the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The that can be used to abort the operation. + /// The number of authorizations corresponding to the criteria that were marked as revoked. + ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken = default); + + /// + /// Revokes all the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The authorization type. + /// The that can be used to abort the operation. + /// The number of authorizations corresponding to the criteria that were marked as revoked. + ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken = default); + /// /// Revokes all the authorizations associated with the specified application identifier. /// diff --git a/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs b/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs index f8e4c0ba..3ddcfde7 100644 --- a/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs +++ b/src/OpenIddict.Abstractions/Managers/IOpenIddictTokenManager.cs @@ -409,6 +409,37 @@ public interface IOpenIddictTokenManager /// The number of tokens that were removed. ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default); + /// + /// Revokes all the tokens corresponding to the specified + /// subject and associated with the application identifier. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The that can be used to abort the operation. + /// The number of tokens corresponding to the criteria that were marked as revoked. + ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken = default); + + /// + /// Revokes all the tokens matching the specified parameters. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The token status. + /// The that can be used to abort the operation. + /// The number of tokens corresponding to the criteria that were marked as revoked. + ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken = default); + + /// + /// Revokes all the tokens matching the specified parameters. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The token status. + /// The token type. + /// The that can be used to abort the operation. + /// The number of tokens corresponding to the criteria that were marked as revoked. + ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken = default); + /// /// Revokes all the tokens associated with the specified application identifier. /// diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs index 01121dac..cac2c297 100644 --- a/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictAuthorizationStore.cs @@ -279,6 +279,37 @@ public interface IOpenIddictAuthorizationStore where TAuthorizat /// The number of authorizations that were removed. ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken); + /// + /// Revokes all the authorizations corresponding to the specified + /// subject and associated with the application identifier. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The that can be used to abort the operation. + /// The number of authorizations corresponding to the criteria that were marked as revoked. + ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken); + + /// + /// Revokes all the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The that can be used to abort the operation. + /// The number of authorizations corresponding to the criteria that were marked as revoked. + ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken); + + /// + /// Revokes all the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The authorization type. + /// The that can be used to abort the operation. + /// The number of authorizations corresponding to the criteria that were marked as revoked. + ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken); + /// /// Revokes all the authorizations associated with the specified application identifier. /// diff --git a/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs b/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs index af65b61f..b2cde75d 100644 --- a/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs +++ b/src/OpenIddict.Abstractions/Stores/IOpenIddictTokenStore.cs @@ -326,6 +326,37 @@ public interface IOpenIddictTokenStore where TToken : class /// The number of tokens that were removed. ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken); + /// + /// Revokes all the tokens corresponding to the specified + /// subject and associated with the application identifier. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The that can be used to abort the operation. + /// The number of tokens corresponding to the criteria that were marked as revoked. + ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken); + + /// + /// Revokes all the tokens matching the specified parameters. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The token status. + /// The that can be used to abort the operation. + /// The number of tokens corresponding to the criteria that were marked as revoked. + ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken); + + /// + /// Revokes all the tokens matching the specified parameters. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The token status. + /// The token type. + /// The that can be used to abort the operation. + /// The number of tokens corresponding to the criteria that were marked as revoked. + ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken); + /// /// Revokes all the tokens associated with the specified application identifier. /// diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index 54f3aad9..e2c4b556 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -1028,6 +1028,91 @@ public class OpenIddictAuthorizationManager : IOpenIddictAuthori public virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default) => Store.PruneAsync(threshold, cancellationToken); + /// + /// Revokes all the authorizations corresponding to the specified + /// subject and associated with the application identifier. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The that can be used to abort the operation. + /// The number of authorizations corresponding to the criteria that were marked as revoked. + public virtual ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + return Store.RevokeAsync(subject, client, cancellationToken); + } + + /// + /// Revokes all the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The that can be used to abort the operation. + /// The number of authorizations corresponding to the criteria that were marked as revoked. + public virtual ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + return Store.RevokeAsync(subject, client, status, cancellationToken); + } + + /// + /// Revokes all the authorizations matching the specified parameters. + /// + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The authorization status. + /// The authorization type. + /// The that can be used to abort the operation. + /// The number of authorizations corresponding to the criteria that were marked as revoked. + public virtual ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } + + return Store.RevokeAsync(subject, client, status, type, cancellationToken); + } + /// /// Revokes all the authorizations associated with the specified application identifier. /// @@ -1369,6 +1454,18 @@ public class OpenIddictAuthorizationManager : IOpenIddictAuthori ValueTask IOpenIddictAuthorizationManager.PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) => PruneAsync(threshold, cancellationToken); + /// + ValueTask IOpenIddictAuthorizationManager.RevokeAsync(string subject, string client, CancellationToken cancellationToken) + => RevokeAsync(subject, client, cancellationToken); + + /// + ValueTask IOpenIddictAuthorizationManager.RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) + => RevokeAsync(subject, client, status, cancellationToken); + + /// + ValueTask IOpenIddictAuthorizationManager.RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) + => RevokeAsync(subject, client, status, type, cancellationToken); + /// ValueTask IOpenIddictAuthorizationManager.RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken) => RevokeByApplicationIdAsync(identifier, cancellationToken); diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index d7438298..c16ba726 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -1055,6 +1055,91 @@ public class OpenIddictTokenManager : IOpenIddictTokenManager where TTok public virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default) => Store.PruneAsync(threshold, cancellationToken); + /// + /// Revokes all the tokens corresponding to the specified + /// subject and associated with the application identifier. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The that can be used to abort the operation. + /// The number of tokens corresponding to the criteria that were marked as revoked. + public virtual ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + return Store.RevokeAsync(subject, client, cancellationToken); + } + + /// + /// Revokes all the tokens matching the specified parameters. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The token status. + /// The that can be used to abort the operation. + /// The number of tokens corresponding to the criteria that were marked as revoked. + public virtual ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + return Store.RevokeAsync(subject, client, status, cancellationToken); + } + + /// + /// Revokes all the tokens matching the specified parameters. + /// + /// The subject associated with the token. + /// The client associated with the token. + /// The token status. + /// The token type. + /// The that can be used to abort the operation. + /// The number of tokens corresponding to the criteria that were marked as revoked. + public virtual ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } + + return Store.RevokeAsync(subject, client, status, type, cancellationToken); + } + /// /// Revokes all the tokens associated with the specified application identifier. /// @@ -1533,6 +1618,18 @@ public class OpenIddictTokenManager : IOpenIddictTokenManager where TTok ValueTask IOpenIddictTokenManager.PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken) => PruneAsync(threshold, cancellationToken); + /// + ValueTask IOpenIddictTokenManager.RevokeAsync(string subject, string client, CancellationToken cancellationToken) + => RevokeAsync(subject, client, cancellationToken); + + /// + ValueTask IOpenIddictTokenManager.RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) + => RevokeAsync(subject, client, status, cancellationToken); + + /// + ValueTask IOpenIddictTokenManager.RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) + => RevokeAsync(subject, client, status, type, cancellationToken); + /// ValueTask IOpenIddictTokenManager.RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken) => RevokeByApplicationIdAsync(identifier, cancellationToken); diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs index d6020f77..b0ba2e06 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkAuthorizationStore.cs @@ -658,6 +658,182 @@ public class OpenIddictEntityFrameworkAuthorizationStore + public virtual async ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + var key = ConvertIdentifierFromString(client); + + List? exceptions = null; + + var result = 0L; + + foreach (var authorization in await (from authorization in Authorizations + where authorization.Application!.Id!.Equals(key) && 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 RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + var key = ConvertIdentifierFromString(client); + + List? exceptions = null; + + var result = 0L; + + foreach (var authorization in await (from authorization in Authorizations + where authorization.Application!.Id!.Equals(key) && + authorization.Subject == subject && + authorization.Status == status + 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 RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } + + var key = ConvertIdentifierFromString(client); + + List? exceptions = null; + + var result = 0L; + + foreach (var authorization in await (from authorization in Authorizations + where authorization.Application!.Id!.Equals(key) && + authorization.Subject == subject && + authorization.Status == status && + authorization.Type == type + 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 RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken) { diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs index d790bf44..56edf7bb 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictEntityFrameworkTokenStore.cs @@ -659,6 +659,182 @@ public class OpenIddictEntityFrameworkTokenStore + public virtual async ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + var key = ConvertIdentifierFromString(client); + + List? exceptions = null; + + var result = 0L; + + foreach (var token in await (from token in Tokens + where token.Application!.Id!.Equals(key) && 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 RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + var key = ConvertIdentifierFromString(client); + + List? exceptions = null; + + var result = 0L; + + foreach (var token in await (from token in Tokens + where token.Application!.Id!.Equals(key) && + token.Subject == subject && + token.Status == status + 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 RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } + + var key = ConvertIdentifierFromString(client); + + List? exceptions = null; + + var result = 0L; + + foreach (var token in await (from token in Tokens + where token.Application!.Id!.Equals(key) && + token.Subject == subject && + token.Status == status && + token.Type == type + 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 RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken) { diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs index 21115ab0..71bb8d54 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictEntityFrameworkCoreAuthorizationStore.cs @@ -254,9 +254,10 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken) { - // Note: due to a bug in Entity Framework Core's query visitor, the authorizations can't be - // filtered using authorization.Application.Id.Equals(key). To work around this issue, - // this method is overridden to use an explicit join before applying the equality check. + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations + // can't be filtered using authorization.Application.Id.Equals(key). To work around + // this issue, this query uses use an explicit join to apply the equality check. + // // See https://github.com/openiddict/openiddict-core/issues/499 for more information. var key = ConvertIdentifierFromString(client); @@ -409,9 +413,10 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore + public virtual async ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + var key = ConvertIdentifierFromString(client); + +#if SUPPORTS_BULK_DBSET_OPERATIONS + if (!Options.CurrentValue.DisableBulkOperations) + { + return await ( + from authorization in Authorizations + where authorization.Subject == subject && 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; + + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations + // can't be filtered using authorization.Application.Id.Equals(key). To work around + // this issue, this query uses use an explicit join to apply the equality check. + // + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + + foreach (var authorization in await (from authorization in Authorizations.AsTracking() + where authorization.Subject == subject + join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id + where 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 RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + var key = ConvertIdentifierFromString(client); + +#if SUPPORTS_BULK_DBSET_OPERATIONS + if (!Options.CurrentValue.DisableBulkOperations) + { + return await ( + from authorization in Authorizations + where authorization.Subject == subject && + authorization.Status == status && + 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; + + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations + // can't be filtered using authorization.Application.Id.Equals(key). To work around + // this issue, this query uses use an explicit join to apply the equality check. + // + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + + foreach (var authorization in await (from authorization in Authorizations.AsTracking() + where authorization.Subject == subject && authorization.Status == status + join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id + where 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 RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } + + var key = ConvertIdentifierFromString(client); + +#if SUPPORTS_BULK_DBSET_OPERATIONS + if (!Options.CurrentValue.DisableBulkOperations) + { + return await ( + from authorization in Authorizations + where authorization.Subject == subject && + authorization.Status == status && + authorization.Type == type && + 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; + + // Note: due to a bug in Entity Framework Core's query visitor, the authorizations + // can't be filtered using authorization.Application.Id.Equals(key). To work around + // this issue, this query uses use an explicit join to apply the equality check. + // + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + + foreach (var authorization in await (from authorization in Authorizations.AsTracking() + where authorization.Subject == subject && authorization.Status == status && authorization.Type == type + join application in Applications.AsTracking() on authorization.Application!.Id equals application.Id + where 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 RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken) { @@ -826,8 +1070,15 @@ public class OpenIddictEntityFrameworkCoreAuthorizationStore + public virtual async ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + var key = ConvertIdentifierFromString(client); + +#if SUPPORTS_BULK_DBSET_OPERATIONS + if (!Options.CurrentValue.DisableBulkOperations) + { + return await ( + from token in Tokens + where token.Subject == subject && 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; + + // Note: due to a bug in Entity Framework Core's query visitor, the tokens + // can't be filtered using token.Application.Id.Equals(key). To work around + // this issue, this query uses use an explicit join to apply the equality check. + // + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + + foreach (var token in await (from token in Tokens.AsTracking() + where token.Subject == subject + join application in Applications.AsTracking() on token.Application!.Id equals application.Id + where 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 RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + var key = ConvertIdentifierFromString(client); + +#if SUPPORTS_BULK_DBSET_OPERATIONS + if (!Options.CurrentValue.DisableBulkOperations) + { + return await ( + from token in Tokens + where token.Subject == subject && token.Status == status && 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; + + // Note: due to a bug in Entity Framework Core's query visitor, the tokens + // can't be filtered using token.Application.Id.Equals(key). To work around + // this issue, this query uses use an explicit join to apply the equality check. + // + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + + foreach (var token in await (from token in Tokens.AsTracking() + where token.Subject == subject && token.Status == status + join application in Applications.AsTracking() on token.Application!.Id equals application.Id + where 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 RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } + + var key = ConvertIdentifierFromString(client); + +#if SUPPORTS_BULK_DBSET_OPERATIONS + if (!Options.CurrentValue.DisableBulkOperations) + { + return await ( + from token in Tokens + where token.Subject == subject && + token.Status == status && + token.Type == type && + 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; + + // Note: due to a bug in Entity Framework Core's query visitor, the tokens + // can't be filtered using token.Application.Id.Equals(key). To work around + // this issue, this query uses use an explicit join to apply the equality check. + // + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + + foreach (var token in await (from token in Tokens.AsTracking() + where token.Subject == subject && token.Status == status && token.Type == type + join application in Applications.AsTracking() on token.Application!.Id equals application.Id + where 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 RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken) { @@ -775,8 +1017,15 @@ public class OpenIddictEntityFrameworkCoreTokenStore : IOpenIddictAu var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); await foreach (var authorization in collection.Find(authorization => - authorization.Subject == subject && - authorization.ApplicationId == ObjectId.Parse(client)).ToAsyncEnumerable(cancellationToken)) + authorization.ApplicationId == ObjectId.Parse(client) && + authorization.Subject == subject).ToAsyncEnumerable(cancellationToken)) { yield return authorization; } @@ -160,8 +160,8 @@ public class OpenIddictMongoDbAuthorizationStore : IOpenIddictAu var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); await foreach (var authorization in collection.Find(authorization => - authorization.Subject == subject && authorization.ApplicationId == ObjectId.Parse(client) && + authorization.Subject == subject && authorization.Status == status).ToAsyncEnumerable(cancellationToken)) { yield return authorization; @@ -202,8 +202,8 @@ public class OpenIddictMongoDbAuthorizationStore : IOpenIddictAu var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); await foreach (var authorization in collection.Find(authorization => - authorization.Subject == subject && authorization.ApplicationId == ObjectId.Parse(client) && + authorization.Subject == subject && authorization.Status == status && authorization.Type == type).ToAsyncEnumerable(cancellationToken)) { @@ -248,8 +248,8 @@ public class OpenIddictMongoDbAuthorizationStore : IOpenIddictAu // Note: Enumerable.All() is deliberately used without the extension method syntax to ensure // ImmutableArrayExtensions.All() (which is not supported by MongoDB) is not used instead. await foreach (var authorization in collection.Find(authorization => - authorization.Subject == subject && authorization.ApplicationId == ObjectId.Parse(client) && + authorization.Subject == subject && authorization.Status == status && authorization.Type == type && Enumerable.All(scopes, scope => authorization.Scopes!.Contains(scope))).ToAsyncEnumerable(cancellationToken)) @@ -549,6 +549,95 @@ public class OpenIddictMongoDbAuthorizationStore : IOpenIddictAu return result; } + /// + public virtual async ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); + + return (await collection.UpdateManyAsync( + filter : authorization => authorization.Subject == subject && authorization.ApplicationId == ObjectId.Parse(client), + update : Builders.Update.Set(authorization => authorization.Status, Statuses.Revoked), + options : null, + cancellationToken: cancellationToken)).MatchedCount; + } + + /// + public virtual async ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); + + return (await collection.UpdateManyAsync( + filter : authorization => authorization.ApplicationId == ObjectId.Parse(client) && + authorization.Subject == subject && + authorization.Status == status, + update : Builders.Update.Set(authorization => authorization.Status, Statuses.Revoked), + options : null, + cancellationToken: cancellationToken)).MatchedCount; + } + + /// + public virtual async ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } + + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.AuthorizationsCollectionName); + + return (await collection.UpdateManyAsync( + filter : authorization => authorization.ApplicationId == ObjectId.Parse(client) && + authorization.Subject == subject && + authorization.Status == status && + authorization.Type == type, + update : Builders.Update.Set(authorization => authorization.Status, Statuses.Revoked), + options : null, + cancellationToken: cancellationToken)).MatchedCount; + } + /// public virtual async ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken) { diff --git a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs index 9396d42c..f14bd5ac 100644 --- a/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs +++ b/src/OpenIddict.MongoDb/Stores/OpenIddictMongoDbTokenStore.cs @@ -586,6 +586,95 @@ public class OpenIddictMongoDbTokenStore : IOpenIddictTokenStore return result; } + /// + public virtual async ValueTask RevokeAsync(string subject, string client, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + + return (await collection.UpdateManyAsync( + filter : token => token.ApplicationId == ObjectId.Parse(client) && token.Subject == subject, + update : Builders.Update.Set(token => token.Status, Statuses.Revoked), + options : null, + cancellationToken: cancellationToken)).MatchedCount; + } + + /// + public virtual async ValueTask RevokeAsync(string subject, string client, string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + + return (await collection.UpdateManyAsync( + filter : token => token.ApplicationId == ObjectId.Parse(client) && + token.Subject == subject && + token.Status == status, + update : Builders.Update.Set(token => token.Status, Statuses.Revoked), + options : null, + cancellationToken: cancellationToken)).MatchedCount; + } + + /// + public virtual async ValueTask RevokeAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type)); + } + + var database = await Context.GetDatabaseAsync(cancellationToken); + var collection = database.GetCollection(Options.CurrentValue.TokensCollectionName); + + return (await collection.UpdateManyAsync( + filter : token => token.ApplicationId == ObjectId.Parse(client) && + token.Subject == subject && + token.Status == status && + token.Type == type, + update : Builders.Update.Set(token => token.Status, Statuses.Revoked), + options : null, + cancellationToken: cancellationToken)).MatchedCount; + } + /// public virtual async ValueTask RevokeByApplicationIdAsync(string identifier, CancellationToken cancellationToken) {