diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index 96989e46..428000b2 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -173,16 +173,17 @@ namespace OpenIddict.Core } /// - /// Retrieves an authorization using its associated subject/client. + /// Retrieves 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. /// /// A that can be used to monitor the asynchronous operation, - /// whose result returns the authorization corresponding to the subject/client. + /// whose result returns the authorizations corresponding to the subject/client. /// - public virtual Task FindAsync([NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken) + public virtual Task> FindAsync([NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(subject)) { diff --git a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs index d4fb16d8..6b54e6df 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs @@ -72,16 +72,17 @@ namespace OpenIddict.Core Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken); /// - /// Retrieves an authorization using its associated subject/client. + /// Retrieves 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. /// /// A that can be used to monitor the asynchronous operation, - /// whose result returns the authorization corresponding to the subject/client. + /// 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 an authorization using its unique identifier. diff --git a/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs index b0778bb8..ef80e777 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs @@ -100,9 +100,16 @@ namespace OpenIddict.Core throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - var key = ConvertIdentifierFromString(identifier); + IQueryable Query(IQueryable applications) + { + var key = ConvertIdentifierFromString(identifier); + + return from application in applications + where application.Id.Equals(key) + select application; + } - return GetAsync(applications => applications.Where(application => application.Id.Equals(key)), cancellationToken); + return GetAsync(Query, cancellationToken); } /// @@ -121,7 +128,14 @@ namespace OpenIddict.Core throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - return GetAsync(applications => applications.Where(application => application.ClientId.Equals(identifier)), cancellationToken); + IQueryable Query(IQueryable applications) + { + return from application in applications + where application.ClientId == identifier + select application; + } + + return GetAsync(Query, cancellationToken); } /// diff --git a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs index 10a5b9f7..551745ce 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs @@ -85,16 +85,17 @@ namespace OpenIddict.Core public abstract Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken); /// - /// Retrieves an authorization using its associated subject/client. + /// Retrieves 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. /// /// A that can be used to monitor the asynchronous operation, - /// whose result returns the authorization corresponding to the subject/client. + /// whose result returns the authorizations corresponding to the subject/client. /// - public virtual Task FindAsync([NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken) + public virtual Task> FindAsync([NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(subject)) { @@ -106,13 +107,18 @@ namespace OpenIddict.Core throw new ArgumentException("The client cannot be null or empty.", nameof(client)); } - var key = ConvertIdentifierFromString(client); + IQueryable Query(IQueryable authorizations) + { + var key = ConvertIdentifierFromString(client); - return GetAsync(authorizations => - from authorization in authorizations - where authorization.Application.Id.Equals(key) - where authorization.Subject == subject - select authorization, cancellationToken); + return from authorization in authorizations + where authorization.Application != null + where authorization.Application.Id.Equals(key) + where authorization.Subject == subject + select authorization; + } + + return ListAsync(Query, cancellationToken); } /// @@ -131,9 +137,16 @@ namespace OpenIddict.Core throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - var key = ConvertIdentifierFromString(identifier); + IQueryable Query(IQueryable authorizations) + { + var key = ConvertIdentifierFromString(identifier); + + return from authorization in authorizations + where authorization.Id.Equals(key) + select authorization; + } - return GetAsync(authorizations => authorizations.Where(authorization => authorization.Id.Equals(key)), cancellationToken); + return GetAsync(Query, cancellationToken); } /// diff --git a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs index ff786bf4..0030bbb0 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs @@ -98,9 +98,17 @@ namespace OpenIddict.Core throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - var key = ConvertIdentifierFromString(identifier); + IQueryable Query(IQueryable tokens) + { + var key = ConvertIdentifierFromString(identifier); + + return from token in tokens + where token.Application != null + where token.Application.Id.Equals(key) + select token; + } - return ListAsync(tokens => tokens.Where(token => token.Application.Id.Equals(key)), cancellationToken); + return ListAsync(Query, cancellationToken); } /// @@ -119,9 +127,17 @@ namespace OpenIddict.Core throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - var key = ConvertIdentifierFromString(identifier); + IQueryable Query(IQueryable tokens) + { + var key = ConvertIdentifierFromString(identifier); + + return from token in tokens + where token.Authorization != null + where token.Authorization.Id.Equals(key) + select token; + } - return ListAsync(tokens => tokens.Where(token => token.Authorization.Id.Equals(key)), cancellationToken); + return ListAsync(Query, cancellationToken); } /// @@ -140,7 +156,14 @@ namespace OpenIddict.Core throw new ArgumentException("The hash cannot be null or empty.", nameof(hash)); } - return GetAsync(tokens => tokens.Where(token => token.Hash == hash), cancellationToken); + IQueryable Query(IQueryable tokens) + { + return from token in tokens + where token.Hash == hash + select token; + } + + return GetAsync(Query, cancellationToken); } /// @@ -159,9 +182,16 @@ namespace OpenIddict.Core throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } - var key = ConvertIdentifierFromString(identifier); + IQueryable Query(IQueryable tokens) + { + var key = ConvertIdentifierFromString(identifier); + + return from token in tokens + where token.Id.Equals(key) + select token; + } - return GetAsync(tokens => tokens.Where(token => token.Id.Equals(key)), cancellationToken); + return GetAsync(Query, cancellationToken); } /// @@ -180,7 +210,14 @@ namespace OpenIddict.Core throw new ArgumentException("The subject cannot be null or empty.", nameof(subject)); } - return ListAsync(tokens => tokens.Where(token => token.Subject == subject), cancellationToken); + IQueryable Query(IQueryable tokens) + { + return from token in tokens + where token.Subject == subject + select token; + } + + return ListAsync(Query, cancellationToken); } /// diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs index 1778ca94..1c77d3f8 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictAuthorizationStore.cs @@ -369,7 +369,7 @@ namespace OpenIddict.EntityFramework // Bind the authorization to the specified application, if applicable. if (!string.IsNullOrEmpty(descriptor.ApplicationId)) { - var application = await Applications.FindAsync(new object[] { ConvertIdentifierFromString(descriptor.ApplicationId) }, cancellationToken); + var application = await Applications.FindAsync(cancellationToken, ConvertIdentifierFromString(descriptor.ApplicationId)); if (application == null) { throw new InvalidOperationException("The application associated with the authorization cannot be found."); diff --git a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs index 1e6135b8..af7d64f8 100644 --- a/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs @@ -430,7 +430,7 @@ namespace OpenIddict.EntityFramework // Bind the token to the specified client application, if applicable. if (!string.IsNullOrEmpty(descriptor.ApplicationId)) { - var application = await Applications.FindAsync(new object[] { ConvertIdentifierFromString(descriptor.ApplicationId) }, cancellationToken); + var application = await Applications.FindAsync(cancellationToken, ConvertIdentifierFromString(descriptor.ApplicationId)); if (application == null) { throw new InvalidOperationException("The application associated with the token cannot be found."); @@ -442,7 +442,7 @@ namespace OpenIddict.EntityFramework // Bind the token to the specified authorization, if applicable. if (!string.IsNullOrEmpty(descriptor.AuthorizationId)) { - var authorization = await Authorizations.FindAsync(new object[] { ConvertIdentifierFromString(descriptor.AuthorizationId) }, cancellationToken); + var authorization = await Authorizations.FindAsync(cancellationToken, ConvertIdentifierFromString(descriptor.AuthorizationId)); if (authorization == null) { throw new InvalidOperationException("The authorization associated with the token cannot be found."); diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs index 89006cd9..96aa244d 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs @@ -187,6 +187,48 @@ namespace OpenIddict.EntityFrameworkCore await Context.SaveChangesAsync(cancellationToken); } + /// + /// Retrieves 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. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the authorizations corresponding to the subject/client. + /// + public override async Task> FindAsync([NotNull] string subject, [NotNull] string client, 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)); + } + + // 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) + { + var key = ConvertIdentifierFromString(client); + + return from authorization in authorizations + where authorization.Subject == subject + join application in applications on authorization.Application.Id equals application.Id + where application.Id.Equals(key) + select authorization; + } + + return ImmutableArray.Create(await Query(Authorizations, Applications).ToArrayAsync(cancellationToken)); + } + /// /// Executes the specified query. /// diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs index 51ac5efc..352d7269 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs @@ -172,6 +172,74 @@ namespace OpenIddict.EntityFrameworkCore return Context.SaveChangesAsync(cancellationToken); } + /// + /// Retrieves the list of tokens corresponding to the specified application identifier. + /// + /// The application identifier associated with the tokens. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the tokens corresponding to the specified application. + /// + public override async Task> FindByApplicationIdAsync([NotNull] string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); + } + + // 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 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 applications, IQueryable tokens) + { + var key = ConvertIdentifierFromString(identifier); + + return from token in tokens + join application in applications on token.Application.Id equals application.Id + where application.Id.Equals(key) + select token; + } + + return ImmutableArray.Create(await Query(Applications, Tokens).ToArrayAsync(cancellationToken)); + } + + /// + /// Retrieves the list of tokens corresponding to the specified authorization identifier. + /// + /// The authorization identifier associated with the tokens. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the tokens corresponding to the specified authorization. + /// + public override async Task> FindByAuthorizationIdAsync([NotNull] string identifier, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(identifier)) + { + throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); + } + + // Note: due to a bug in Entity Framework Core's query visitor, the tokens can't be + // filtered using token.Authorization.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 tokens) + { + var key = ConvertIdentifierFromString(identifier); + + return from token in tokens + join authorization in authorizations on token.Authorization.Id equals authorization.Id + where authorization.Id.Equals(key) + select token; + } + + return ImmutableArray.Create(await Query(Authorizations, Tokens).ToArrayAsync(cancellationToken)); + } + /// /// Executes the specified query. ///