/* * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) * See https://github.com/openiddict/openiddict-core for more information concerning * the license and the contributors participating to this project. */ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.CompilerServices; using System.Security.Claims; using System.Text; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OpenIddict.Abstractions; using static OpenIddict.Abstractions.OpenIddictExceptions; namespace OpenIddict.Core { /// /// Provides methods allowing to manage the authorizations stored in the store. /// /// The type of the Authorization entity. public class OpenIddictAuthorizationManager : IOpenIddictAuthorizationManager where TAuthorization : class { public OpenIddictAuthorizationManager( [NotNull] IOpenIddictAuthorizationCache cache, [NotNull] IOpenIddictAuthorizationStoreResolver resolver, [NotNull] ILogger> logger, [NotNull] IOptionsMonitor options) { Cache = cache; Store = resolver.Get(); Logger = logger; Options = options; } /// /// Gets the cache associated with the current manager. /// protected IOpenIddictAuthorizationCache Cache { get; } /// /// Gets the logger associated with the current manager. /// protected ILogger Logger { get; } /// /// Gets the options associated with the current manager. /// protected IOptionsMonitor Options { get; } /// /// Gets the store associated with the current manager. /// protected IOpenIddictAuthorizationStore Store { get; } /// /// Determines the number of authorizations that exist in the database. /// /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the number of authorizations in the database. /// public virtual ValueTask CountAsync(CancellationToken cancellationToken = default) => Store.CountAsync(cancellationToken); /// /// Determines the number of authorizations that match the specified query. /// /// The result type. /// The query to execute. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the number of authorizations that match the specified query. /// public virtual ValueTask CountAsync( [NotNull] Func, IQueryable> query, CancellationToken cancellationToken = default) { if (query == null) { throw new ArgumentNullException(nameof(query)); } return Store.CountAsync(query, cancellationToken); } /// /// Creates a new authorization. /// /// The application to create. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// public virtual async ValueTask CreateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { 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); } var results = await ValidateAsync(authorization, cancellationToken).ToListAsync(cancellationToken); if (results.Any(result => result != ValidationResult.Success)) { var builder = new StringBuilder(); builder.AppendLine("One or more validation error(s) occurred while trying to create a new authorization:"); builder.AppendLine(); foreach (var result in results) { builder.AppendLine(result.ErrorMessage); } throw new OpenIddictExceptions.ValidationException(builder.ToString(), results.ToImmutableArray()); } await Store.CreateAsync(authorization, cancellationToken); if (!Options.CurrentValue.DisableEntityCaching) { await Cache.AddAsync(authorization, cancellationToken); } } /// /// Creates a new authorization based on the specified descriptor. /// /// The authorization descriptor. /// 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 async ValueTask CreateAsync( [NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default) { if (descriptor == null) { throw new ArgumentNullException(nameof(descriptor)); } var authorization = await Store.InstantiateAsync(cancellationToken); if (authorization == null) { throw new InvalidOperationException("An error occurred while trying to create a new authorization."); } await PopulateAsync(authorization, descriptor, cancellationToken); await CreateAsync(authorization, cancellationToken); 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 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 authorization. /// public virtual ValueTask CreateAsync( [NotNull] ClaimsPrincipal principal, [NotNull] string subject, [NotNull] string client, [NotNull] string type, ImmutableArray scopes, 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)); } if (string.IsNullOrEmpty(type)) { throw new ArgumentException("The type cannot be null or empty.", nameof(type)); } var descriptor = new OpenIddictAuthorizationDescriptor { ApplicationId = client, Principal = principal, Status = OpenIddictConstants.Statuses.Valid, Subject = subject, Type = type }; descriptor.Scopes.UnionWith(scopes); return CreateAsync(descriptor, cancellationToken); } /// /// Removes an existing authorization. /// /// The authorization to delete. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// public virtual async ValueTask DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } if (!Options.CurrentValue.DisableEntityCaching) { await Cache.RemoveAsync(authorization, cancellationToken); } await Store.DeleteAsync(authorization, 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. /// The authorizations corresponding to the subject/client. public virtual IAsyncEnumerable FindAsync( [NotNull] string subject, [NotNull] string client, 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)); } var authorizations = Options.CurrentValue.DisableEntityCaching ? Store.FindAsync(subject, client, cancellationToken) : Cache.FindAsync(subject, client, cancellationToken); // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. if (Options.CurrentValue.DisableAdditionalFiltering) { return authorizations; } return authorizations.WhereAwait(async authorization => string.Equals( await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)); } /// /// 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. /// The authorizations corresponding to the criteria. public virtual IAsyncEnumerable FindAsync( [NotNull] string subject, [NotNull] string client, [NotNull] string status, 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(status)); } var authorizations = Options.CurrentValue.DisableEntityCaching ? Store.FindAsync(subject, client, status, cancellationToken) : Cache.FindAsync(subject, client, status, cancellationToken); if (Options.CurrentValue.DisableAdditionalFiltering) { return authorizations; } // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. return authorizations.WhereAwait(async authorization => string.Equals( await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)); } /// /// 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 that can be used to abort the operation. /// The authorizations corresponding to the criteria. public virtual IAsyncEnumerable FindAsync( [NotNull] string subject, [NotNull] string client, [NotNull] string status, [NotNull] string type, 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(status)); } if (string.IsNullOrEmpty(type)) { throw new ArgumentException("The type cannot be null or empty.", nameof(type)); } var authorizations = Options.CurrentValue.DisableEntityCaching ? Store.FindAsync(subject, client, status, type, cancellationToken) : Cache.FindAsync(subject, client, status, type, cancellationToken); if (Options.CurrentValue.DisableAdditionalFiltering) { return authorizations; } return authorizations.WhereAwait(async authorization => string.Equals( await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)); } /// /// 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. /// The authorizations corresponding to the criteria. public virtual IAsyncEnumerable 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(status)); } if (string.IsNullOrEmpty(type)) { throw new ArgumentException("The type cannot be null or empty.", nameof(type)); } var authorizations = Options.CurrentValue.DisableEntityCaching ? Store.FindAsync(subject, client, status, type, scopes, cancellationToken) : Cache.FindAsync(subject, client, status, type, scopes, cancellationToken); if (Options.CurrentValue.DisableAdditionalFiltering) { return authorizations; } // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. return authorizations.WhereAwait(async authorization => string.Equals( await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal) && await HasScopesAsync(authorization, scopes, cancellationToken)); } /// /// Retrieves the list of authorizations corresponding to the specified application identifier. /// /// The application identifier associated with the authorizations. /// The that can be used to abort the operation. /// The authorizations corresponding to the specified application. public virtual IAsyncEnumerable FindByApplicationIdAsync( [NotNull] string identifier, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(identifier)) { throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } var authorizations = Options.CurrentValue.DisableEntityCaching ? Store.FindByApplicationIdAsync(identifier, cancellationToken) : Cache.FindByApplicationIdAsync(identifier, cancellationToken); if (Options.CurrentValue.DisableAdditionalFiltering) { return authorizations; } // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. return authorizations.WhereAwait(async authorization => string.Equals( await Store.GetApplicationIdAsync(authorization, cancellationToken), identifier, StringComparison.Ordinal)); } /// /// Retrieves an authorization using its unique identifier. /// /// The unique identifier 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 identifier. /// public virtual async ValueTask FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(identifier)) { throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } var authorization = Options.CurrentValue.DisableEntityCaching ? await Store.FindByIdAsync(identifier, cancellationToken) : await Cache.FindByIdAsync(identifier, cancellationToken); if (authorization == null) { return null; } // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. if (!Options.CurrentValue.DisableAdditionalFiltering && !string.Equals(await Store.GetIdAsync(authorization, cancellationToken), identifier, StringComparison.Ordinal)) { return null; } return authorization; } /// /// Retrieves all the authorizations corresponding to the specified subject. /// /// The subject associated with the authorization. /// The that can be used to abort the operation. /// The authorizations corresponding to the specified subject. public virtual IAsyncEnumerable FindBySubjectAsync( [NotNull] string subject, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(subject)) { throw new ArgumentException("The subject cannot be null or empty.", nameof(subject)); } var authorizations = Options.CurrentValue.DisableEntityCaching ? Store.FindBySubjectAsync(subject, cancellationToken) : Cache.FindBySubjectAsync(subject, cancellationToken); if (Options.CurrentValue.DisableAdditionalFiltering) { return authorizations; } // SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default. // To ensure a case-sensitive comparison is enforced independently of the database/table/query collation // used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here. return authorizations.WhereAwait(async authorization => string.Equals( await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)); } /// /// Retrieves the optional application identifier associated with an authorization. /// /// 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 application identifier associated with the authorization. /// public virtual ValueTask GetApplicationIdAsync( [NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } return Store.GetApplicationIdAsync(authorization, cancellationToken); } /// /// Executes the specified query and returns the first element. /// /// The result type. /// The query to execute. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the first element returned when executing the query. /// public virtual ValueTask GetAsync( [NotNull] Func, IQueryable> query, CancellationToken cancellationToken = default) { if (query == null) { throw new ArgumentNullException(nameof(query)); } return GetAsync((authorizations, state) => state(authorizations), query, cancellationToken); } /// /// Executes the specified query and returns the first element. /// /// The state type. /// The result type. /// The query to execute. /// The optional state. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the first element returned when executing the query. /// public virtual ValueTask GetAsync( [NotNull] Func, TState, IQueryable> query, [CanBeNull] TState state, CancellationToken cancellationToken = default) { if (query == null) { throw new ArgumentNullException(nameof(query)); } return Store.GetAsync(query, state, cancellationToken); } /// /// Retrieves the unique identifier associated with an authorization. /// /// 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 unique identifier associated with the authorization. /// public virtual ValueTask GetIdAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } return Store.GetIdAsync(authorization, cancellationToken); } /// /// Retrieves the scopes associated with an authorization. /// /// 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 scopes associated with the specified authorization. /// public virtual ValueTask> GetScopesAsync( [NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } return Store.GetScopesAsync(authorization, cancellationToken); } /// /// Retrieves the status associated with an authorization. /// /// 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 status associated with the specified authorization. /// public virtual ValueTask GetStatusAsync( [NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } return Store.GetStatusAsync(authorization, cancellationToken); } /// /// Retrieves the subject associated with an authorization. /// /// 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 subject associated with the specified authorization. /// public virtual ValueTask GetSubjectAsync( [NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } return Store.GetSubjectAsync(authorization, cancellationToken); } /// /// Retrieves the type associated with an authorization. /// /// 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 type associated with the specified authorization. /// public virtual ValueTask GetTypeAsync( [NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } return Store.GetTypeAsync(authorization, cancellationToken); } /// /// Determines whether the specified scopes are included in the authorization. /// /// The authorization. /// The scopes. /// The that can be used to abort the operation. /// true if the scopes are included in the authorization, false otherwise. public virtual async ValueTask HasScopesAsync([NotNull] TAuthorization authorization, ImmutableArray scopes, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } return (await Store.GetScopesAsync(authorization, cancellationToken)) .ToImmutableHashSet(StringComparer.Ordinal) .IsSupersetOf(scopes); } /// /// Determines whether a given authorization has the specified status. /// /// The authorization. /// The expected status. /// The that can be used to abort the operation. /// true if the authorization has the specified status, false otherwise. public virtual async ValueTask HasStatusAsync([NotNull] TAuthorization authorization, [NotNull] string status, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } if (string.IsNullOrEmpty(status)) { throw new ArgumentException("The status cannot be null or empty.", nameof(status)); } return string.Equals(await Store.GetStatusAsync(authorization, cancellationToken), status, StringComparison.OrdinalIgnoreCase); } /// /// Executes the specified query and returns all the corresponding elements. /// /// The number of results to return. /// The number of results to skip. /// The that can be used to abort the operation. /// All the elements returned when executing the specified query. public virtual IAsyncEnumerable ListAsync( [CanBeNull] int? count = null, [CanBeNull] int? offset = null, CancellationToken cancellationToken = default) => Store.ListAsync(count, offset, cancellationToken); /// /// Executes the specified query and returns all the corresponding elements. /// /// The result type. /// The query to execute. /// The that can be used to abort the operation. /// All the elements returned when executing the specified query. public virtual IAsyncEnumerable ListAsync( [NotNull] Func, IQueryable> query, CancellationToken cancellationToken = default) { if (query == null) { throw new ArgumentNullException(nameof(query)); } return ListAsync((authorizations, state) => state(authorizations), query, cancellationToken); } /// /// Executes the specified query and returns all the corresponding elements. /// /// The state type. /// The result type. /// The query to execute. /// The optional state. /// The that can be used to abort the operation. /// All the elements returned when executing the specified query. public virtual IAsyncEnumerable ListAsync( [NotNull] Func, TState, IQueryable> query, [CanBeNull] TState state, CancellationToken cancellationToken = default) { if (query == null) { throw new ArgumentNullException(nameof(query)); } return Store.ListAsync(query, state, cancellationToken); } /// /// Populates the authorization using the specified descriptor. /// /// The authorization. /// The descriptor. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// public virtual async ValueTask PopulateAsync([NotNull] TAuthorization authorization, [NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } if (descriptor == null) { throw new ArgumentNullException(nameof(descriptor)); } await Store.SetApplicationIdAsync(authorization, descriptor.ApplicationId, cancellationToken); await Store.SetScopesAsync(authorization, ImmutableArray.CreateRange(descriptor.Scopes), cancellationToken); await Store.SetStatusAsync(authorization, descriptor.Status, cancellationToken); await Store.SetSubjectAsync(authorization, descriptor.Subject, cancellationToken); await Store.SetTypeAsync(authorization, descriptor.Type, cancellationToken); } /// /// Populates the specified descriptor using the properties exposed by the authorization. /// /// The descriptor. /// The authorization. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// public virtual async ValueTask PopulateAsync( [NotNull] OpenIddictAuthorizationDescriptor descriptor, [NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (descriptor == null) { throw new ArgumentNullException(nameof(descriptor)); } if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } descriptor.ApplicationId = await Store.GetApplicationIdAsync(authorization, cancellationToken); descriptor.Scopes.Clear(); descriptor.Scopes.UnionWith(await Store.GetScopesAsync(authorization, cancellationToken)); descriptor.Status = await Store.GetStatusAsync(authorization, cancellationToken); descriptor.Subject = await Store.GetSubjectAsync(authorization, cancellationToken); descriptor.Type = await Store.GetTypeAsync(authorization, cancellationToken); } /// /// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no valid/nonexpired token attached. /// /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// public virtual ValueTask PruneAsync(CancellationToken cancellationToken = default) => Store.PruneAsync(cancellationToken); /// /// Sets the application identifier associated with an authorization. /// /// The authorization. /// The unique identifier associated with the client application. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// public virtual async ValueTask SetApplicationIdAsync( [NotNull] TAuthorization authorization, [CanBeNull] string identifier, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } await Store.SetApplicationIdAsync(authorization, identifier, cancellationToken); await UpdateAsync(authorization, cancellationToken); } /// /// Tries to revoke an authorization. /// /// The authorization to revoke. /// The that can be used to abort the operation. /// true if the authorization was successfully revoked, false otherwise. public virtual async ValueTask TryRevokeAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } var status = await Store.GetStatusAsync(authorization, cancellationToken); if (string.Equals(status, OpenIddictConstants.Statuses.Revoked, StringComparison.OrdinalIgnoreCase)) { return true; } await Store.SetStatusAsync(authorization, OpenIddictConstants.Statuses.Revoked, cancellationToken); try { await UpdateAsync(authorization, cancellationToken); Logger.LogInformation("The authorization '{Identifier}' was successfully revoked.", await Store.GetIdAsync(authorization, cancellationToken)); return true; } catch (ConcurrencyException exception) { Logger.LogDebug(exception, "A concurrency exception occurred while trying to revoke the authorization '{Identifier}'.", await Store.GetIdAsync(authorization, cancellationToken)); return false; } catch (Exception exception) { Logger.LogWarning(exception, "An exception occurred while trying to revoke the authorization '{Identifier}'.", await Store.GetIdAsync(authorization, cancellationToken)); return false; } } /// /// Updates an existing authorization. /// /// The authorization to update. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// public virtual async ValueTask UpdateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } var results = await ValidateAsync(authorization, cancellationToken).ToListAsync(cancellationToken); if (results.Any(result => result != ValidationResult.Success)) { var builder = new StringBuilder(); builder.AppendLine("One or more validation error(s) occurred while trying to update an existing authorization:"); builder.AppendLine(); foreach (var result in results) { builder.AppendLine(result.ErrorMessage); } throw new OpenIddictExceptions.ValidationException(builder.ToString(), results.ToImmutableArray()); } await Store.UpdateAsync(authorization, cancellationToken); if (!Options.CurrentValue.DisableEntityCaching) { await Cache.RemoveAsync(authorization, cancellationToken); await Cache.AddAsync(authorization, cancellationToken); } } /// /// Updates an existing authorization. /// /// The authorization to update. /// The descriptor used to update the authorization. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// public virtual async ValueTask UpdateAsync([NotNull] TAuthorization authorization, [NotNull] OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } if (descriptor == null) { throw new ArgumentNullException(nameof(descriptor)); } await PopulateAsync(authorization, descriptor, cancellationToken); await UpdateAsync(authorization, cancellationToken); } /// /// Validates the authorization to ensure it's in a consistent state. /// /// The authorization. /// The that can be used to abort the operation. /// The validation error encountered when validating the authorization. public virtual async IAsyncEnumerable ValidateAsync( [NotNull] TAuthorization authorization, [EnumeratorCancellation] CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } var type = await Store.GetTypeAsync(authorization, cancellationToken); if (string.IsNullOrEmpty(type)) { yield return new ValidationResult("The authorization type cannot be null or empty."); } else if (!string.Equals(type, OpenIddictConstants.AuthorizationTypes.AdHoc, StringComparison.OrdinalIgnoreCase) && !string.Equals(type, OpenIddictConstants.AuthorizationTypes.Permanent, StringComparison.OrdinalIgnoreCase)) { yield return new ValidationResult("The specified authorization type is not supported by the default token manager."); } if (string.IsNullOrEmpty(await Store.GetStatusAsync(authorization, cancellationToken))) { yield return new ValidationResult("The status cannot be null or empty."); } // Ensure that the scopes are not null or empty and do not contain spaces. foreach (var scope in await Store.GetScopesAsync(authorization, cancellationToken)) { if (string.IsNullOrEmpty(scope)) { yield return new ValidationResult("Scopes cannot be null or empty."); break; } if (scope.Contains(OpenIddictConstants.Separators.Space[0])) { yield return new ValidationResult("Scopes cannot contain spaces."); break; } } } ValueTask IOpenIddictAuthorizationManager.CountAsync(CancellationToken cancellationToken) => CountAsync(cancellationToken); ValueTask IOpenIddictAuthorizationManager.CountAsync(Func, IQueryable> query, CancellationToken cancellationToken) => CountAsync(query, cancellationToken); async ValueTask IOpenIddictAuthorizationManager.CreateAsync(ClaimsPrincipal principal, string subject, string client, string type, ImmutableArray scopes, CancellationToken cancellationToken) => await CreateAsync(principal, subject, client, type, scopes, cancellationToken); async ValueTask IOpenIddictAuthorizationManager.CreateAsync(OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) => await CreateAsync(descriptor, cancellationToken); ValueTask IOpenIddictAuthorizationManager.CreateAsync(object authorization, CancellationToken cancellationToken) => CreateAsync((TAuthorization) authorization, cancellationToken); ValueTask IOpenIddictAuthorizationManager.DeleteAsync(object authorization, CancellationToken cancellationToken) => DeleteAsync((TAuthorization) authorization, cancellationToken); IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, CancellationToken cancellationToken) => FindAsync(subject, client, cancellationToken).OfType(); IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, string status, CancellationToken cancellationToken) => FindAsync(subject, client, status, cancellationToken).OfType(); IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, string status, string type, CancellationToken cancellationToken) => FindAsync(subject, client, status, type, cancellationToken).OfType(); IAsyncEnumerable IOpenIddictAuthorizationManager.FindAsync(string subject, string client, string status, string type, ImmutableArray scopes, CancellationToken cancellationToken) => FindAsync(subject, client, status, type, scopes, cancellationToken).OfType(); IAsyncEnumerable IOpenIddictAuthorizationManager.FindByApplicationIdAsync(string identifier, CancellationToken cancellationToken) => FindByApplicationIdAsync(identifier, cancellationToken).OfType(); async ValueTask IOpenIddictAuthorizationManager.FindByIdAsync(string identifier, CancellationToken cancellationToken) => await FindByIdAsync(identifier, cancellationToken); IAsyncEnumerable IOpenIddictAuthorizationManager.FindBySubjectAsync(string subject, CancellationToken cancellationToken) => FindBySubjectAsync(subject, cancellationToken).OfType(); ValueTask IOpenIddictAuthorizationManager.GetApplicationIdAsync(object authorization, CancellationToken cancellationToken) => GetApplicationIdAsync((TAuthorization) authorization, cancellationToken); ValueTask IOpenIddictAuthorizationManager.GetAsync(Func, IQueryable> query, CancellationToken cancellationToken) => GetAsync(query, cancellationToken); ValueTask IOpenIddictAuthorizationManager.GetAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) => GetAsync(query, state, cancellationToken); ValueTask IOpenIddictAuthorizationManager.GetIdAsync(object authorization, CancellationToken cancellationToken) => GetIdAsync((TAuthorization) authorization, cancellationToken); ValueTask> IOpenIddictAuthorizationManager.GetScopesAsync(object authorization, CancellationToken cancellationToken) => GetScopesAsync((TAuthorization) authorization, cancellationToken); ValueTask IOpenIddictAuthorizationManager.GetStatusAsync(object authorization, CancellationToken cancellationToken) => GetStatusAsync((TAuthorization) authorization, cancellationToken); ValueTask IOpenIddictAuthorizationManager.GetSubjectAsync(object authorization, CancellationToken cancellationToken) => GetSubjectAsync((TAuthorization) authorization, cancellationToken); ValueTask IOpenIddictAuthorizationManager.GetTypeAsync(object authorization, CancellationToken cancellationToken) => GetTypeAsync((TAuthorization) authorization, cancellationToken); ValueTask IOpenIddictAuthorizationManager.HasScopesAsync(object authorization, ImmutableArray scopes, CancellationToken cancellationToken) => HasScopesAsync((TAuthorization) authorization, scopes, cancellationToken); ValueTask IOpenIddictAuthorizationManager.HasStatusAsync(object authorization, string status, CancellationToken cancellationToken) => HasStatusAsync((TAuthorization) authorization, status, cancellationToken); IAsyncEnumerable IOpenIddictAuthorizationManager.ListAsync(int? count, int? offset, CancellationToken cancellationToken) => ListAsync(count, offset, cancellationToken).OfType(); IAsyncEnumerable IOpenIddictAuthorizationManager.ListAsync(Func, IQueryable> query, CancellationToken cancellationToken) => ListAsync(query, cancellationToken); IAsyncEnumerable IOpenIddictAuthorizationManager.ListAsync(Func, TState, IQueryable> query, TState state, CancellationToken cancellationToken) => ListAsync(query, state, cancellationToken); ValueTask IOpenIddictAuthorizationManager.PopulateAsync(OpenIddictAuthorizationDescriptor descriptor, object authorization, CancellationToken cancellationToken) => PopulateAsync(descriptor, (TAuthorization) authorization, cancellationToken); ValueTask IOpenIddictAuthorizationManager.PopulateAsync(object authorization, OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) => PopulateAsync((TAuthorization) authorization, descriptor, cancellationToken); ValueTask IOpenIddictAuthorizationManager.PruneAsync(CancellationToken cancellationToken) => PruneAsync(cancellationToken); ValueTask IOpenIddictAuthorizationManager.SetApplicationIdAsync(object authorization, string identifier, CancellationToken cancellationToken) => SetApplicationIdAsync((TAuthorization) authorization, identifier, cancellationToken); ValueTask IOpenIddictAuthorizationManager.TryRevokeAsync(object authorization, CancellationToken cancellationToken) => TryRevokeAsync((TAuthorization) authorization, cancellationToken); ValueTask IOpenIddictAuthorizationManager.UpdateAsync(object authorization, CancellationToken cancellationToken) => UpdateAsync((TAuthorization) authorization, cancellationToken); ValueTask IOpenIddictAuthorizationManager.UpdateAsync(object authorization, OpenIddictAuthorizationDescriptor descriptor, CancellationToken cancellationToken) => UpdateAsync((TAuthorization) authorization, descriptor, cancellationToken); IAsyncEnumerable IOpenIddictAuthorizationManager.ValidateAsync(object authorization, CancellationToken cancellationToken) => ValidateAsync((TAuthorization) authorization, cancellationToken); } }