From efce0af4990ebd8b589f82c243cf7d640feb24d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Thu, 15 Feb 2018 16:25:28 +0100 Subject: [PATCH] Introduce OpenIddictApplication.ConsentType and add new authorization helpers --- samples/Mvc.Server/Startup.cs | 5 +- .../OpenIddictApplicationDescriptor.cs | 6 + .../Managers/OpenIddictApplicationManager.cs | 27 ++++ .../OpenIddictAuthorizationManager.cs | 131 ++++++++++++++++++ .../Managers/OpenIddictScopeManager.cs | 20 ++- .../Managers/OpenIddictTokenManager.cs | 6 + src/OpenIddict.Core/OpenIddictConstants.cs | 7 + .../Stores/IOpenIddictApplicationStore.cs | 22 +++ .../Stores/IOpenIddictAuthorizationStore.cs | 27 ++++ .../Stores/OpenIddictApplicationStore.cs | 41 ++++++ .../Stores/OpenIddictAuthorizationStore.cs | 89 +++++++++++- .../Stores/OpenIddictTokenStore.cs | 16 +-- .../Stores/OpenIddictAuthorizationStore.cs | 65 ++++++++- .../Stores/OpenIddictTokenStore.cs | 4 +- .../OpenIddictApplication.cs | 6 + src/OpenIddict/OpenIddictExtensions.cs | 4 +- 16 files changed, 451 insertions(+), 25 deletions(-) diff --git a/samples/Mvc.Server/Startup.cs b/samples/Mvc.Server/Startup.cs index 12b4fdfb..6ad7131b 100644 --- a/samples/Mvc.Server/Startup.cs +++ b/samples/Mvc.Server/Startup.cs @@ -90,9 +90,10 @@ namespace Mvc.Server .AllowPasswordFlow() .AllowRefreshTokenFlow(); - // Mark the "email" and "profile" scopes as supported scopes. + // Mark the "email", "profile" and "roles" scopes as supported scopes. options.RegisterScopes(OpenIdConnectConstants.Scopes.Email, - OpenIdConnectConstants.Scopes.Profile); + OpenIdConnectConstants.Scopes.Profile, + OpenIddictConstants.Scopes.Roles); // Make the "client_id" parameter mandatory when sending a token request. options.RequireClientIdentification(); diff --git a/src/OpenIddict.Core/Descriptors/OpenIddictApplicationDescriptor.cs b/src/OpenIddict.Core/Descriptors/OpenIddictApplicationDescriptor.cs index 8f1524cc..ad68de3d 100644 --- a/src/OpenIddict.Core/Descriptors/OpenIddictApplicationDescriptor.cs +++ b/src/OpenIddict.Core/Descriptors/OpenIddictApplicationDescriptor.cs @@ -21,6 +21,12 @@ namespace OpenIddict.Core /// public string ClientSecret { get; set; } + /// + /// Gets or sets the consent type + /// associated with the application. + /// + public virtual string ConsentType { get; set; } + /// /// Gets or sets the display name /// associated with the application. diff --git a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs index 84c3ed7d..e60ab678 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictApplicationManager.cs @@ -370,6 +370,31 @@ namespace OpenIddict.Core return type; } + /// + /// Retrieves the consent type associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the consent type of the application (by default, "explicit"). + /// + public virtual async Task GetConsentTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken = default) + { + if (application == null) + { + throw new ArgumentNullException(nameof(application)); + } + + var type = await Store.GetConsentTypeAsync(application, cancellationToken); + if (string.IsNullOrEmpty(type)) + { + return OpenIddictConstants.ConsentTypes.Explicit; + } + + return type; + } + /// /// Retrieves the display name associated with an application. /// @@ -799,6 +824,7 @@ namespace OpenIddict.Core { ClientId = await Store.GetClientIdAsync(application, cancellationToken), ClientSecret = secret, + ConsentType = await Store.GetConsentTypeAsync(application, cancellationToken), DisplayName = await Store.GetDisplayNameAsync(application, cancellationToken), Type = await Store.GetClientTypeAsync(application, cancellationToken) }; @@ -1161,6 +1187,7 @@ namespace OpenIddict.Core await Store.SetClientIdAsync(application, descriptor.ClientId, cancellationToken); await Store.SetClientSecretAsync(application, descriptor.ClientSecret, cancellationToken); await Store.SetClientTypeAsync(application, descriptor.Type, cancellationToken); + await Store.SetConsentTypeAsync(application, descriptor.ConsentType, cancellationToken); await Store.SetDisplayNameAsync(application, descriptor.DisplayName, cancellationToken); await Store.SetPermissionsAsync(application, ImmutableArray.CreateRange(descriptor.Permissions), cancellationToken); await Store.SetPostLogoutRedirectUrisAsync(application, ImmutableArray.CreateRange( diff --git a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs index 71a2bf68..7994f4c3 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictAuthorizationManager.cs @@ -5,9 +5,11 @@ */ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; @@ -88,6 +90,12 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(authorization)); } + // If no status was explicitly specified, assume that the authorization is valid. + if (string.IsNullOrEmpty(await Store.GetStatusAsync(authorization, cancellationToken))) + { + await Store.SetStatusAsync(authorization, OpenIddictConstants.Statuses.Valid, cancellationToken); + } + // If no type was explicitly specified, assume that the authorization is a permanent authorization. if (string.IsNullOrEmpty(await Store.GetTypeAsync(authorization, cancellationToken))) { @@ -131,6 +139,58 @@ namespace OpenIddict.Core return authorization; } + /// + /// Creates a new permanent authorization based on the specified parameters. + /// + /// The principal associated with the authorization. + /// The subject associated with the authorization. + /// The client associated with the authorization. + /// The minimal scopes associated with the authorization. + /// The authentication properties 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. + /// + public virtual Task CreateAsync( + [NotNull] ClaimsPrincipal principal, [NotNull] string subject, + [NotNull] string client, ImmutableArray scopes, + [CanBeNull] ImmutableDictionary properties, CancellationToken cancellationToken = default) + { + if (principal == null) + { + throw new ArgumentNullException(nameof(principal)); + } + + 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)); + } + + var descriptor = new OpenIddictAuthorizationDescriptor + { + ApplicationId = client, + Principal = principal, + Subject = subject + }; + + descriptor.Scopes.UnionWith(scopes); + + if (properties != null) + { + foreach (var property in properties) + { + descriptor.Properties.Add(property); + } + } + + return CreateAsync(descriptor, cancellationToken); + } + /// /// Removes an existing authorization. /// @@ -176,6 +236,57 @@ 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 status associated with the authorization. + /// The type associated with the authorization. + /// 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 subject/client. + /// + public virtual async Task> FindAsync( + [NotNull] string subject, [NotNull] string client, + [NotNull] string status, [NotNull] string type, + ImmutableArray scopes, CancellationToken cancellationToken = default) + { + 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)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException("The type cannot be null or empty.", nameof(client)); + } + + var authorizations = ImmutableArray.CreateBuilder(); + + foreach (var authorization in await Store.FindAsync(subject, client, status, type, cancellationToken)) + { + if (await HasScopesAsync(authorization, scopes, cancellationToken)) + { + authorizations.Add(authorization); + } + } + + return authorizations.ToImmutable(); + } + /// /// Retrieves an authorization using its unique identifier. /// @@ -195,6 +306,26 @@ namespace OpenIddict.Core return Store.FindByIdAsync(identifier, cancellationToken); } + /// + /// Retrieves all the authorizations corresponding to the specified subject. + /// + /// The subject 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 specified subject. + /// + public virtual Task> FindBySubjectAsync( + [NotNull] string subject, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException("The subject cannot be null or empty.", nameof(subject)); + } + + return Store.FindBySubjectAsync(subject, cancellationToken); + } + /// /// Retrieves the optional application identifier associated with an authorization. /// diff --git a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs index bd3659e2..b0eecfb4 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs @@ -501,11 +501,29 @@ namespace OpenIddict.Core var results = ImmutableArray.CreateBuilder(); - if (string.IsNullOrEmpty(await Store.GetNameAsync(scope, cancellationToken))) + var name = await Store.GetNameAsync(scope, cancellationToken); + if (string.IsNullOrEmpty(name)) { results.Add(new ValidationResult("The scope name cannot be null or empty.")); } + else if (name.Contains(OpenIddictConstants.Separators.Space)) + { + results.Add(new ValidationResult("The scope name cannot contain spaces.")); + } + + else + { + // Ensure the name is not already used for a different name. + var other = await Store.FindByNameAsync(name, cancellationToken); + if (other != null && !string.Equals( + await Store.GetIdAsync(other, cancellationToken), + await Store.GetIdAsync(scope, cancellationToken), StringComparison.Ordinal)) + { + results.Add(new ValidationResult("A scope with the same name already exists.")); + } + } + return results.ToImmutable(); } diff --git a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs index f0a52317..96094279 100644 --- a/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs +++ b/src/OpenIddict.Core/Managers/OpenIddictTokenManager.cs @@ -90,6 +90,12 @@ namespace OpenIddict.Core throw new ArgumentNullException(nameof(token)); } + // If no status was explicitly specified, assume that the token is valid. + if (string.IsNullOrEmpty(await Store.GetStatusAsync(token, cancellationToken))) + { + await Store.SetStatusAsync(token, OpenIddictConstants.Statuses.Valid, cancellationToken); + } + var results = await ValidateAsync(token, cancellationToken); if (results.Any(result => result != ValidationResult.Success)) { diff --git a/src/OpenIddict.Core/OpenIddictConstants.cs b/src/OpenIddict.Core/OpenIddictConstants.cs index 5ff54b19..c4185636 100644 --- a/src/OpenIddict.Core/OpenIddictConstants.cs +++ b/src/OpenIddict.Core/OpenIddictConstants.cs @@ -26,6 +26,13 @@ namespace OpenIddict.Core public const string Public = "public"; } + public static class ConsentTypes + { + public const string Explicit = "explicit"; + public const string External = "external"; + public const string Implicit = "implicit"; + } + public static class Environment { public const string AuthorizationRequest = "openiddict-authorization-request:"; diff --git a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs index 7d44a177..11f11698 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictApplicationStore.cs @@ -157,6 +157,17 @@ namespace OpenIddict.Core /// Task GetClientTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken); + /// + /// Retrieves the consent type associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the consent type of the application (by default, "explicit"). + /// + Task GetConsentTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken); + /// /// Retrieves the display name associated with an application. /// @@ -296,6 +307,17 @@ namespace OpenIddict.Core /// Task SetClientTypeAsync([NotNull] TApplication application, [CanBeNull] string type, CancellationToken cancellationToken); + /// + /// Sets the consent type associated with an application. + /// + /// The application. + /// The consent type associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + Task SetConsentTypeAsync([NotNull] TApplication application, [CanBeNull] string type, CancellationToken cancellationToken); + /// /// Sets the display name associated with an application. /// diff --git a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs index 7c97cac9..43f0552a 100644 --- a/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Core/Stores/IOpenIddictAuthorizationStore.cs @@ -75,6 +75,22 @@ namespace OpenIddict.Core /// 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 status associated with the authorization. + /// The type 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. + /// + Task> FindAsync( + [NotNull] string subject, [NotNull] string client, + [NotNull] string status, [NotNull] string type, CancellationToken cancellationToken); + /// /// Retrieves an authorization using its unique identifier. /// @@ -86,6 +102,17 @@ namespace OpenIddict.Core /// Task FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken); + /// + /// Retrieves all the authorizations corresponding to the specified subject. + /// + /// The subject 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 specified subject. + /// + Task> FindBySubjectAsync([NotNull] string subject, CancellationToken cancellationToken); + /// /// Retrieves the optional application identifier associated with an authorization. /// diff --git a/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs index d183a01e..dabc46e4 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictApplicationStore.cs @@ -307,6 +307,25 @@ namespace OpenIddict.Core return Task.FromResult(application.Type); } + /// + /// Retrieves the consent type associated with an application. + /// + /// The application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation, + /// whose result returns the consent type of the application (by default, "explicit"). + /// + public virtual Task GetConsentTypeAsync([NotNull] TApplication application, CancellationToken cancellationToken) + { + if (application == null) + { + throw new ArgumentNullException(nameof(application)); + } + + return Task.FromResult(application.ConsentType); + } + /// /// Retrieves the display name associated with an application. /// @@ -603,6 +622,28 @@ namespace OpenIddict.Core return Task.CompletedTask; } + /// + /// Sets the consent type associated with an application. + /// + /// The application. + /// The consent type associated with the application. + /// The that can be used to abort the operation. + /// + /// A that can be used to monitor the asynchronous operation. + /// + public virtual Task SetConsentTypeAsync([NotNull] TApplication application, + [CanBeNull] string type, CancellationToken cancellationToken) + { + if (application == null) + { + throw new ArgumentNullException(nameof(application)); + } + + application.ConsentType = type; + + return Task.CompletedTask; + } + /// /// Sets the display name associated with an application. /// diff --git a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs index e6b468eb..e33fe5e0 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictAuthorizationStore.cs @@ -104,7 +104,8 @@ namespace OpenIddict.Core /// A that can be used to monitor the asynchronous operation, /// 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)) { @@ -118,9 +119,9 @@ namespace OpenIddict.Core IQueryable Query(IQueryable authorizations, TKey key, string principal) => from authorization in authorizations - where authorization.Application != null - where authorization.Application.Id.Equals(key) - where authorization.Subject == principal + where authorization.Application != null && + authorization.Application.Id.Equals(key) && + authorization.Subject == principal select authorization; return ListAsync( @@ -128,6 +129,57 @@ 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 status associated with the authorization. + /// The type 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 virtual Task> FindAsync( + [NotNull] string subject, [NotNull] string client, + [NotNull] string status, [NotNull] string type, 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)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException("The type cannot be null or empty.", nameof(client)); + } + + IQueryable Query(IQueryable authorizations, + TKey key, string principal, string state, string kind) + => from authorization in authorizations + where authorization.Application != null && + authorization.Application.Id.Equals(key) && + authorization.Subject == principal && + authorization.Status == state && + authorization.Type == kind + select authorization; + + return ListAsync( + (authorizations, state) => Query(authorizations, state.key, state.principal, state.state, state.kind), + (key: ConvertIdentifierFromString(client), principal: subject, state: status, kind: type), cancellationToken); + } + /// /// Retrieves an authorization using its unique identifier. /// @@ -152,6 +204,31 @@ namespace OpenIddict.Core return GetAsync((authorizations, key) => Query(authorizations, key), ConvertIdentifierFromString(identifier), cancellationToken); } + /// + /// Retrieves all the authorizations corresponding to the specified subject. + /// + /// The subject 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 specified subject. + /// + public virtual Task> FindBySubjectAsync( + [NotNull] string subject, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(subject)) + { + throw new ArgumentException("The subject cannot be null or empty.", nameof(subject)); + } + + IQueryable Query(IQueryable authorizations, string principal) + => from authorization in authorizations + where authorization.Subject == principal + select authorization; + + return ListAsync((authorizations, principal) => Query(authorizations, principal), subject, cancellationToken); + } + /// /// Retrieves the optional application identifier associated with an authorization. /// @@ -175,8 +252,8 @@ namespace OpenIddict.Core IQueryable Query(IQueryable authorizations, TKey key) => from element in authorizations - where element.Id.Equals(key) - where element.Application != null + where element.Id.Equals(key) && + element.Application != null select element.Application.Id; return ConvertIdentifierToString(await GetAsync( diff --git a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs index 73142d9c..679e89e1 100644 --- a/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.Core/Stores/OpenIddictTokenStore.cs @@ -108,8 +108,8 @@ namespace OpenIddict.Core IQueryable Query(IQueryable tokens, TKey key) => from token in tokens - where token.Application != null - where token.Application.Id.Equals(key) + where token.Application != null && + token.Application.Id.Equals(key) select token; return ListAsync((tokens, key) => Query(tokens, key), ConvertIdentifierFromString(identifier), cancellationToken); @@ -133,8 +133,8 @@ namespace OpenIddict.Core IQueryable Query(IQueryable tokens, TKey key) => from token in tokens - where token.Authorization != null - where token.Authorization.Id.Equals(key) + where token.Authorization != null && + token.Authorization.Id.Equals(key) select token; return ListAsync((tokens, key) => Query(tokens, key), ConvertIdentifierFromString(identifier), cancellationToken); @@ -252,8 +252,8 @@ namespace OpenIddict.Core IQueryable Query(IQueryable tokens, TKey key) => from element in tokens - where element.Id.Equals(key) - where element.Application != null + where element.Id.Equals(key) && + element.Application != null select element.Application.Id; return ConvertIdentifierToString(await GetAsync((tokens, key) => Query(tokens, key), token.Id, cancellationToken)); @@ -282,8 +282,8 @@ namespace OpenIddict.Core IQueryable Query(IQueryable tokens, TKey key) => from element in tokens - where element.Id.Equals(key) - where element.Authorization != null + where element.Id.Equals(key) && + element.Authorization != null select element.Authorization.Id; return ConvertIdentifierToString(await GetAsync((tokens, key) => Query(tokens, key), token.Id, cancellationToken)); diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs index db9e18c6..1abf8662 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictAuthorizationStore.cs @@ -188,7 +188,8 @@ namespace OpenIddict.EntityFrameworkCore /// 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) + public override async Task> FindAsync( + [NotNull] string subject, [NotNull] string client, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(subject)) { @@ -205,7 +206,8 @@ namespace OpenIddict.EntityFrameworkCore // 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) + IQueryable Query(IQueryable authorizations, + IQueryable applications, TKey key, string principal) => from authorization in authorizations.Include(authorization => authorization.Application) where authorization.Subject == principal join application in applications on authorization.Application.Id equals application.Id @@ -216,6 +218,61 @@ 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 status associated with the authorization. + /// The type 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, + [NotNull] string status, [NotNull] string type, 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)); + } + + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException("The type 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, TKey key, string principal, string state, string kind) + => from authorization in authorizations.Include(authorization => authorization.Application) + where authorization.Subject == principal && + authorization.Status == state && + authorization.Type == kind + 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, type).ToListAsync(cancellationToken)); + } + /// /// Retrieves an authorization using its unique identifier. /// @@ -233,8 +290,8 @@ namespace OpenIddict.EntityFrameworkCore } var authorization = (from entry in Context.ChangeTracker.Entries() - where entry.Entity != null - where entry.Entity.Id.Equals(ConvertIdentifierFromString(identifier)) + where entry.Entity != null && + entry.Entity.Id.Equals(ConvertIdentifierFromString(identifier)) select entry.Entity).FirstOrDefault(); if (authorization != null) diff --git a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs index 92ab42a2..6a272c5c 100644 --- a/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs +++ b/src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictTokenStore.cs @@ -242,8 +242,8 @@ namespace OpenIddict.EntityFrameworkCore } var token = (from entry in Context.ChangeTracker.Entries() - where entry.Entity != null - where entry.Entity.Id.Equals(ConvertIdentifierFromString(identifier)) + where entry.Entity != null && + entry.Entity.Id.Equals(ConvertIdentifierFromString(identifier)) select entry.Entity).FirstOrDefault(); if (token != null) diff --git a/src/OpenIddict.Models/OpenIddictApplication.cs b/src/OpenIddict.Models/OpenIddictApplication.cs index 99ae4588..0665b573 100644 --- a/src/OpenIddict.Models/OpenIddictApplication.cs +++ b/src/OpenIddict.Models/OpenIddictApplication.cs @@ -56,6 +56,12 @@ namespace OpenIddict.Models /// public virtual string ConcurrencyToken { get; set; } = Guid.NewGuid().ToString(); + /// + /// Gets or sets the consent type + /// associated with the current application. + /// + public virtual string ConsentType { get; set; } + /// /// Gets or sets the display name /// associated with the current application. diff --git a/src/OpenIddict/OpenIddictExtensions.cs b/src/OpenIddict/OpenIddictExtensions.cs index 021800f3..33443e64 100644 --- a/src/OpenIddict/OpenIddictExtensions.cs +++ b/src/OpenIddict/OpenIddictExtensions.cs @@ -690,8 +690,8 @@ namespace Microsoft.Extensions.DependencyInjection /// /// Rejects authorization and token requests that specify scopes that have not been - /// registered in the database using - /// or . + /// registered using or + /// . /// /// The services builder used by OpenIddict to register new services. public static OpenIddictBuilder EnableScopeValidation([NotNull] this OpenIddictBuilder builder)