/* * 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.Immutable; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OpenIddict.Abstractions; namespace OpenIddict.Core { /// /// Provides methods allowing to manage the authorizations stored in the store. /// /// The type of the Authorization entity. public class OpenIddictAuthorizationManager where TAuthorization : class { public OpenIddictAuthorizationManager( [NotNull] IOpenIddictAuthorizationStoreResolver resolver, [NotNull] ILogger> logger, [NotNull] IOptionsMonitor options) { Store = resolver.Get(); Logger = logger; Options = options; } /// /// 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 Task CountAsync(CancellationToken cancellationToken = default) { return 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 Task 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 Task 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); if (results.Any(result => result != ValidationResult.Success)) { throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, authorization); } await Store.CreateAsync(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 Task 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 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, [NotNull] string type, 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)); } 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); if (properties != null) { foreach (var property in properties) { descriptor.Properties.Add(property); } } 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 Task DeleteAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } return 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. /// /// 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, 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)); } // 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. var authorizations = await Store.FindAsync(subject, client, cancellationToken); if (authorizations.IsEmpty) { return ImmutableArray.Create(); } var builder = ImmutableArray.CreateBuilder(authorizations.Length); foreach (var authorization in authorizations) { if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) { builder.Add(authorization); } } return builder.Count == builder.Capacity ? builder.MoveToImmutable() : builder.ToImmutable(); } /// /// Retrieves the authorizations matching the specified parameters. /// /// The subject associated with the authorization. /// The client associated with the authorization. /// The authorization status. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the authorizations corresponding to the criteria. /// public virtual async Task> FindAsync( [NotNull] string subject, [NotNull] string client, [NotNull] string status, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(subject)) { throw new ArgumentException("The subject cannot be null or empty.", nameof(subject)); } if (string.IsNullOrEmpty(client)) { throw new ArgumentException("The client identifier cannot be null or empty.", nameof(client)); } if (string.IsNullOrEmpty(status)) { throw new ArgumentException("The status cannot be null or empty.", nameof(status)); } // 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. var authorizations = await Store.FindAsync(subject, client, status, cancellationToken); if (authorizations.IsEmpty) { return ImmutableArray.Create(); } var builder = ImmutableArray.CreateBuilder(authorizations.Length); foreach (var authorization in authorizations) { if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) { builder.Add(authorization); } } return builder.Count == builder.Capacity ? builder.MoveToImmutable() : builder.ToImmutable(); } /// /// 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. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the authorizations corresponding to the criteria. /// public virtual async Task> FindAsync( [NotNull] string subject, [NotNull] string client, [NotNull] string status, [NotNull] string type, 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)); } // 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. var authorizations = await Store.FindAsync(subject, client, status, type, cancellationToken); if (authorizations.IsEmpty) { return ImmutableArray.Create(); } var builder = ImmutableArray.CreateBuilder(authorizations.Length); foreach (var authorization in authorizations) { if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) { builder.Add(authorization); } } return builder.Count == builder.Capacity ? builder.MoveToImmutable() : builder.ToImmutable(); } /// /// Retrieves the authorizations matching the specified parameters. /// /// The subject associated with the authorization. /// The client associated with the authorization. /// The authorization status. /// The authorization type. /// The minimal scopes associated with the authorization. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the authorizations corresponding to the criteria. /// public virtual async Task> FindAsync( [NotNull] string subject, [NotNull] string client, [NotNull] string status, [NotNull] string type, ImmutableArray scopes, CancellationToken cancellationToken = default) { var authorizations = await FindAsync(subject, client, status, type, cancellationToken); if (authorizations.IsEmpty) { return ImmutableArray.Create(); } var builder = ImmutableArray.CreateBuilder(authorizations.Length); foreach (var authorization in authorizations) { if (await HasScopesAsync(authorization, scopes, cancellationToken)) { builder.Add(authorization); } } return builder.Count == builder.Capacity ? builder.MoveToImmutable() : builder.ToImmutable(); } /// /// 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 Task FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(identifier)) { throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier)); } 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 async Task> FindBySubjectAsync( [NotNull] string subject, CancellationToken cancellationToken = default) { if (string.IsNullOrEmpty(subject)) { throw new ArgumentException("The subject cannot be null or empty.", nameof(subject)); } // 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. var authorizations = await Store.FindBySubjectAsync(subject, cancellationToken); if (authorizations.IsEmpty) { return ImmutableArray.Create(); } var builder = ImmutableArray.CreateBuilder(authorizations.Length); foreach (var authorization in authorizations) { if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal)) { builder.Add(authorization); } } return builder.Count == builder.Capacity ? builder.MoveToImmutable() : builder.ToImmutable(); } /// /// 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 Task GetAsync( [NotNull] Func, IQueryable> query, CancellationToken cancellationToken = default) { 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 Task 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 Task 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 is ad hoc. /// /// The authorization. /// The that can be used to abort the operation. /// true if the authorization is ad hoc, false otherwise. public async Task IsAdHocAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } var type = await GetTypeAsync(authorization, cancellationToken); if (string.IsNullOrEmpty(type)) { return false; } return string.Equals(type, OpenIddictConstants.AuthorizationTypes.AdHoc, StringComparison.OrdinalIgnoreCase); } /// /// Determines whether a given authorization is permanent. /// /// The authorization. /// The that can be used to abort the operation. /// true if the authorization is permanent, false otherwise. public async Task IsPermanentAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } var type = await GetTypeAsync(authorization, cancellationToken); if (string.IsNullOrEmpty(type)) { return false; } return string.Equals(type, OpenIddictConstants.AuthorizationTypes.Permanent, StringComparison.OrdinalIgnoreCase); } /// /// Determines whether a given authorization has been revoked. /// /// The authorization. /// The that can be used to abort the operation. /// true if the authorization has been revoked, false otherwise. public virtual async Task IsRevokedAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } var status = await Store.GetStatusAsync(authorization, cancellationToken); if (string.IsNullOrEmpty(status)) { return false; } return string.Equals(status, OpenIddictConstants.Statuses.Revoked, StringComparison.OrdinalIgnoreCase); } /// /// Determines whether a given authorization is valid. /// /// The authorization. /// The that can be used to abort the operation. /// true if the authorization is valid, false otherwise. public virtual async Task IsValidAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } var status = await Store.GetStatusAsync(authorization, cancellationToken); if (string.IsNullOrEmpty(status)) { return false; } return string.Equals(status, OpenIddictConstants.Statuses.Valid, 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. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns all the elements returned when executing the specified query. /// public virtual Task> ListAsync( [CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken = default) { return 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. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns all the elements returned when executing the specified query. /// public virtual Task> ListAsync( [NotNull] Func, IQueryable> query, CancellationToken cancellationToken = default) { 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. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns all the elements returned when executing the specified query. /// public virtual Task> 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); } /// /// Removes the ad-hoc authorizations that are marked as invalid or have no valid token attached. /// /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// public virtual Task PruneAsync(CancellationToken cancellationToken = default) => Store.PruneAsync(cancellationToken); /// /// Revokes an authorization. /// /// The authorization to revoke. /// The that can be used to abort the operation. /// A that can be used to monitor the asynchronous operation. public virtual async Task RevokeAsync([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)) { await Store.SetStatusAsync(authorization, OpenIddictConstants.Statuses.Revoked, cancellationToken); var results = await ValidateAsync(authorization, cancellationToken); if (results.Any(result => result != ValidationResult.Success)) { throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, authorization); } await UpdateAsync(authorization, 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 Task 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); } /// /// 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 Task UpdateAsync([NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } var results = await ValidateAsync(authorization, cancellationToken); if (results.Any(result => result != ValidationResult.Success)) { throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, authorization); } await Store.UpdateAsync(authorization, cancellationToken); } /// /// Updates an existing authorization. /// /// The authorization to update. /// The delegate used to update the authorization based on the given descriptor. /// The that can be used to abort the operation. /// /// A that can be used to monitor the asynchronous operation. /// public virtual async Task UpdateAsync([NotNull] TAuthorization authorization, [NotNull] Func operation, CancellationToken cancellationToken = default) { if (operation == null) { throw new ArgumentNullException(nameof(operation)); } var descriptor = new OpenIddictAuthorizationDescriptor { ApplicationId = await Store.GetApplicationIdAsync(authorization, cancellationToken), Status = await Store.GetStatusAsync(authorization, cancellationToken), Subject = await Store.GetSubjectAsync(authorization, cancellationToken), Type = await Store.GetTypeAsync(authorization, cancellationToken) }; foreach (var scope in await Store.GetScopesAsync(authorization, cancellationToken)) { descriptor.Scopes.Add(scope); } await operation(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. /// /// A that can be used to monitor the asynchronous operation, /// whose result returns the validation error encountered when validating the authorization. /// public virtual async Task> ValidateAsync( [NotNull] TAuthorization authorization, CancellationToken cancellationToken = default) { if (authorization == null) { throw new ArgumentNullException(nameof(authorization)); } var builder = ImmutableArray.CreateBuilder(); var type = await Store.GetTypeAsync(authorization, cancellationToken); if (string.IsNullOrEmpty(type)) { builder.Add(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)) { builder.Add(new ValidationResult("The specified authorization type is not supported by the default token manager.")); } if (string.IsNullOrEmpty(await Store.GetStatusAsync(authorization, cancellationToken))) { builder.Add(new ValidationResult("The status cannot be null or empty.")); } if (string.IsNullOrEmpty(await Store.GetSubjectAsync(authorization, cancellationToken))) { builder.Add(new ValidationResult("The subject 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)) { builder.Add(new ValidationResult("Scopes cannot be null or empty.")); break; } if (scope.Contains(OpenIddictConstants.Separators.Space)) { builder.Add(new ValidationResult("Scopes cannot contain spaces.")); break; } } return builder.Count == builder.Capacity ? builder.MoveToImmutable() : builder.ToImmutable(); } /// /// 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. /// protected virtual async Task 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); } } }