From a5376d46f05577fd1368777105e08c34fb1421ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Mon, 6 Nov 2017 01:51:25 +0100 Subject: [PATCH 1/3] Update IOpenIddictTokenStore.FindByApplicationIdAsync()/FindByAuthorizationIdAsync() to work around an Entity Framework bug --- .../Stores/OpenIddictApplicationStore.cs | 20 +++++- .../Stores/OpenIddictAuthorizationStore.cs | 28 +++++--- .../Stores/OpenIddictTokenStore.cs | 53 ++++++++++++--- .../Stores/OpenIddictAuthorizationStore.cs | 41 +++++++++++ .../Stores/OpenIddictTokenStore.cs | 68 +++++++++++++++++++ 5 files changed, 191 insertions(+), 19 deletions(-) diff --git a/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs index 0a3e298c..da5aaa0a 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 308eaa6f..331d3fa7 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs @@ -106,13 +106,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 from authorization in authorizations + where authorization.Application != null + where authorization.Application.Id.Equals(key) + where authorization.Subject == subject + select authorization; + } - return GetAsync(authorizations => - from authorization in authorizations - where authorization.Application.Id.Equals(key) - where authorization.Subject == subject - select authorization, cancellationToken); + return GetAsync(Query, cancellationToken); } /// @@ -131,9 +136,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 7b8d0754..25019111 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.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs index 90c0d2a3..777524e0 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs @@ -187,6 +187,47 @@ namespace OpenIddict.EntityFrameworkCore await Context.SaveChangesAsync(cancellationToken); } + /// + /// Retrieves an authorization using its associated subject/client. + /// + /// 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. + /// + public override 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 Query(Authorizations, Applications).SingleOrDefaultAsync(cancellationToken); + } + /// /// Retrieves an authorization using its unique identifier. /// diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs index fab5b859..6f39b161 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)); + } + /// /// Retrieves an token using its unique identifier. /// From 8db7c49a43fdfe5d7d2a5a47056a8be7c3d2fa5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Mon, 6 Nov 2017 15:12:44 +0100 Subject: [PATCH 2/3] Fix the Entity Framework 6.x authorizations/tokens stores to use the FindAsync() method correctly --- .../Stores/OpenIddictAuthorizationStore.cs | 2 +- src/OpenIddict.EntityFramework/Stores/OpenIddictTokenStore.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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."); From 5ce37e4ad20a11582b0591d0f9ca9b4af17b8248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Mon, 6 Nov 2017 19:04:21 +0100 Subject: [PATCH 3/3] Update OpenIddictAuthorizationManager.FindAsync() to return an array of authorizations instead of a single element --- .../Managers/OpenIddictAuthorizationManager.cs | 7 ++++--- .../Stores/IOpenIddictAuthorizationStore.cs | 7 ++++--- .../Stores/OpenIddictAuthorizationStore.cs | 9 +++++---- .../Stores/OpenIddictAuthorizationStore.cs | 9 +++++---- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index d32ea3fe..0617f147 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/OpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs index 331d3fa7..0ff0219d 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)) { @@ -117,7 +118,7 @@ namespace OpenIddict.Core select authorization; } - return GetAsync(Query, cancellationToken); + return ListAsync(Query, cancellationToken); } /// diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs index 777524e0..635c9f91 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs @@ -188,16 +188,17 @@ namespace OpenIddict.EntityFrameworkCore } /// - /// 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 override Task FindAsync([NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken) + public override async Task> FindAsync([NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(subject)) { @@ -225,7 +226,7 @@ namespace OpenIddict.EntityFrameworkCore select authorization; } - return Query(Authorizations, Applications).SingleOrDefaultAsync(cancellationToken); + return ImmutableArray.Create(await Query(Authorizations, Applications).ToArrayAsync(cancellationToken)); } ///