diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index 3ddbd384..23d0dd77 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -233,6 +233,39 @@ namespace OpenIddict.Core return Store.FindAsync(subject, client, cancellationToken); } + /// + /// Retrieves 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. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the authorizations corresponding to the criteria. + /// + public virtual Task> FindAsync( + [NotNull] string subject, [NotNull] string client, + [NotNull] string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException("The subject cannot be null or empty.", nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException("The client identifier cannot be null or empty.", nameof(client)); + } + + if (string.IsNullOrEmpty(status)) + { + throw new ArgumentException("The status cannot be null or empty.", nameof(client)); + } + + return Store.FindAsync(subject, client, status, cancellationToken); + } + /// /// Retrieves the authorizations matching the specified parameters. /// @@ -240,16 +273,14 @@ namespace OpenIddict.Core /// The client associated with the authorization. /// The authorization status. /// The authorization type. - /// The minimal scopes associated with the authorization. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the authorizations corresponding to the criteria. /// - public virtual async Task> FindAsync( + public virtual Task> FindAsync( [NotNull] string subject, [NotNull] string client, - [NotNull] string status, [NotNull] string type, - ImmutableArray scopes, CancellationToken cancellationToken = default) + [NotNull] string status, [NotNull] string type, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(subject)) { @@ -271,9 +302,30 @@ namespace OpenIddict.Core throw new ArgumentException("The type cannot be null or empty.", nameof(client)); } + return Store.FindAsync(subject, client, status, type, cancellationToken); + } + + /// + /// Retrieves 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 minimal scopes associated with the authorization. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the authorizations corresponding to the criteria. + /// + public virtual async Task> FindAsync( + [NotNull] string subject, [NotNull] string client, + [NotNull] string status, [NotNull] string type, + ImmutableArray scopes, CancellationToken cancellationToken = default) + { var authorizations = ImmutableArray.CreateBuilder(); - foreach (var authorization in await Store.FindAsync(subject, client, status, type, cancellationToken)) + foreach (var authorization in await FindAsync(subject, client, status, type, cancellationToken)) { if (await HasScopesAsync(authorization, scopes, cancellationToken)) { diff --git a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs index 847371ea..7c15d92c 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs @@ -73,7 +73,23 @@ namespace OpenIddict.Core /// A that can be used to monitor the asynchronous operation, /// whose result returns the authorizations corresponding to the subject/client. /// - Task> FindAsync([NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken); + Task> FindAsync( + [NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken); + + /// + /// Retrieves 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. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the authorizations corresponding to the criteria. + /// + Task> FindAsync( + [NotNull] string subject, [NotNull] string client, + [NotNull] string status, CancellationToken cancellationToken); /// /// Retrieves the authorizations matching the specified parameters. diff --git a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs index f7dcf14c..a85d2cce 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs @@ -129,6 +129,44 @@ namespace OpenIddict.Core (key: ConvertIdentifierFromString(client), principal: subject), cancellationToken); } + /// + /// Retrieves 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. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the authorizations corresponding to the criteria. + /// + public virtual Task> FindAsync( + [NotNull] string subject, [NotNull] string client, + [NotNull] string status, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException("The subject cannot be null or empty.", nameof(subject)); + } + + if (string.IsNullOrEmpty(client)) + { + throw new ArgumentException("The client cannot be null or empty.", nameof(client)); + } + + IQueryable Query(IQueryable authorizations, TKey key, string principal, string state) + => from authorization in authorizations + where authorization.Application != null && + authorization.Application.Id.Equals(key) && + authorization.Subject == principal && + authorization.Status == state + select authorization; + + return ListAsync( + (authorizations, state) => Query(authorizations, state.key, state.principal, state.state), + (key: ConvertIdentifierFromString(client), principal: subject, state: status), cancellationToken); + } + /// /// Retrieves the authorizations matching the specified parameters. /// diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs index 20f6fa86..a1321bf6 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs @@ -218,6 +218,40 @@ namespace OpenIddict.EntityFrameworkCore Authorizations, Applications, ConvertIdentifierFromString(client), subject).ToListAsync(cancellationToken)); } + /// + /// Retrieves 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. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the authorizations corresponding to the criteria. + /// + public override async Task> FindAsync( + [NotNull] string subject, [NotNull] string client, + [NotNull] string status, 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 overriden to use an explicit join before applying the equality check. + // See https://github.com/openiddict/openiddict-core/issues/499 for more information. + + IQueryable Query(IQueryable authorizations, + IQueryable applications, TKey key, string principal, string state) + => from authorization in authorizations.Include(authorization => authorization.Application) + where authorization.Subject == principal && + authorization.Status == state + join application in applications on authorization.Application.Id equals application.Id + where application.Id.Equals(key) + select authorization; + + return ImmutableArray.CreateRange(await Query( + Authorizations, Applications, ConvertIdentifierFromString(client), subject, status).ToListAsync(cancellationToken)); + } + /// /// Retrieves the authorizations matching the specified parameters. ///