/*
* 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.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using static OpenIddict.Abstractions.OpenIddictExceptions;
namespace OpenIddict.Core;
///
/// Provides methods allowing to manage the authorizations stored in the store.
///
///
/// Applications that do not want to depend on a specific entity type can use the non-generic
/// instead, for which the actual entity type
/// is resolved at runtime based on the default entity type registered in the core options.
///
/// The type of the Authorization entity.
public class OpenIddictAuthorizationManager : IOpenIddictAuthorizationManager where TAuthorization : class
{
public OpenIddictAuthorizationManager(
IOpenIddictAuthorizationCache cache!!,
ILogger> logger!!,
IOptionsMonitor options!!,
IOpenIddictAuthorizationStoreResolver resolver!!)
{
Cache = cache;
Logger = logger;
Options = options;
Store = resolver.Get();
}
///
/// 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(
Func, IQueryable> query!!, CancellationToken cancellationToken = default)
=> 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(TAuthorization authorization!!, CancellationToken cancellationToken = default)
{
// If no status was explicitly specified, assume that the authorization is valid.
if (string.IsNullOrEmpty(await Store.GetStatusAsync(authorization, cancellationToken)))
{
await Store.SetStatusAsync(authorization, Statuses.Valid, cancellationToken);
}
var results = await GetValidationResultsAsync(authorization, cancellationToken);
if (results.Any(result => result != ValidationResult.Success))
{
var builder = new StringBuilder();
builder.AppendLine(SR.GetResourceString(SR.ID0219));
builder.AppendLine();
foreach (var result in results)
{
builder.AppendLine(result.ErrorMessage);
}
throw new OpenIddictExceptions.ValidationException(builder.ToString(), results);
}
await Store.CreateAsync(authorization, cancellationToken);
if (!Options.CurrentValue.DisableEntityCaching)
{
await Cache.AddAsync(authorization, cancellationToken);
}
async Task> GetValidationResultsAsync(
TAuthorization authorization, CancellationToken cancellationToken)
{
var builder = ImmutableArray.CreateBuilder();
await foreach (var result in ValidateAsync(authorization, cancellationToken))
{
builder.Add(result);
}
return builder.ToImmutable();
}
}
///
/// 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(
OpenIddictAuthorizationDescriptor descriptor!!, CancellationToken cancellationToken = default)
{
var authorization = await Store.InstantiateAsync(cancellationToken);
if (authorization is null)
{
throw new InvalidOperationException(SR.GetResourceString(SR.ID0220));
}
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(
ClaimsPrincipal principal!!, string subject, string client,
string type, ImmutableArray scopes, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type));
}
var descriptor = new OpenIddictAuthorizationDescriptor
{
ApplicationId = client,
CreationDate = DateTimeOffset.UtcNow,
Principal = principal,
Status = 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(TAuthorization authorization!!, CancellationToken cancellationToken = default)
{
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(
string subject, string client, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0124), 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;
}
// 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 ExecuteAsync(cancellationToken);
async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var authorization in authorizations)
{
if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal))
{
yield return authorization;
}
}
}
}
///
/// 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(
string subject, string client,
string status, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0199), 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 ExecuteAsync(cancellationToken);
async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var authorization in authorizations)
{
if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal))
{
yield return authorization;
}
}
}
}
///
/// 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(
string subject, string client,
string status, string type, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0200), 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;
}
// 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 ExecuteAsync(cancellationToken);
async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var authorization in authorizations)
{
if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal))
{
yield return authorization;
}
}
}
}
///
/// 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(
string subject, string client,
string status, string type,
ImmutableArray scopes, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0198), nameof(subject));
}
if (string.IsNullOrEmpty(client))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(client));
}
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status));
}
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0200), 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 ExecuteAsync(cancellationToken);
async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var authorization in authorizations)
{
if (!string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal))
{
continue;
}
if (!await HasScopesAsync(authorization, scopes, cancellationToken))
{
continue;
}
yield return authorization;
}
}
}
///
/// 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(
string identifier, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), 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 ExecuteAsync(cancellationToken);
async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var authorization in authorizations)
{
if (string.Equals(await Store.GetApplicationIdAsync(authorization, cancellationToken), identifier, StringComparison.Ordinal))
{
yield return authorization;
}
}
}
}
///
/// 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(string identifier, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(identifier))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0195), nameof(identifier));
}
var authorization = Options.CurrentValue.DisableEntityCaching ?
await Store.FindByIdAsync(identifier, cancellationToken) :
await Cache.FindByIdAsync(identifier, cancellationToken);
if (authorization is 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(
string subject, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(subject))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0198), 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 ExecuteAsync(cancellationToken);
async IAsyncEnumerable ExecuteAsync([EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var authorization in authorizations)
{
if (string.Equals(await Store.GetSubjectAsync(authorization, cancellationToken), subject, StringComparison.Ordinal))
{
yield return authorization;
}
}
}
}
///
/// 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(
TAuthorization authorization!!, CancellationToken cancellationToken = default)
=> 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(
Func, IQueryable> query!!, CancellationToken cancellationToken = default)
=> GetAsync(static (authorizations, query) => query(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(
Func, TState, IQueryable> query!!,
TState state, CancellationToken cancellationToken = default)
=> Store.GetAsync(query, state, cancellationToken);
///
/// Retrieves the creation date 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 creation date associated with the specified authorization.
///
public virtual ValueTask GetCreationDateAsync(
TAuthorization authorization!!, CancellationToken cancellationToken = default)
=> Store.GetCreationDateAsync(authorization, 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(TAuthorization authorization!!, CancellationToken cancellationToken = default)
=> Store.GetIdAsync(authorization, cancellationToken);
///
/// Retrieves the additional properties 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 all the additional properties associated with the authorization.
///
public virtual ValueTask> GetPropertiesAsync(
TAuthorization authorization!!, CancellationToken cancellationToken = default)
=> Store.GetPropertiesAsync(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(
TAuthorization authorization!!, CancellationToken cancellationToken = default)
=> 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(
TAuthorization authorization!!, CancellationToken cancellationToken = default)
=> 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(
TAuthorization authorization!!, CancellationToken cancellationToken = default)
=> 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(
TAuthorization authorization!!, CancellationToken cancellationToken = default)
=> 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(TAuthorization authorization!!,
ImmutableArray scopes, CancellationToken cancellationToken = default)
=> new HashSet(await Store.GetScopesAsync(
authorization, cancellationToken), 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(TAuthorization authorization!!,
string status, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(status))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0199), nameof(status));
}
return string.Equals(await Store.GetStatusAsync(authorization, cancellationToken), status, StringComparison.OrdinalIgnoreCase);
}
///
/// Determines whether a given authorization has the specified type.
///
/// The authorization.
/// The expected type.
/// The that can be used to abort the operation.
/// true if the authorization has the specified type, false otherwise.
public virtual async ValueTask HasTypeAsync(
TAuthorization authorization!!, string type, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(type))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0200), nameof(type));
}
return string.Equals(await Store.GetTypeAsync(authorization, cancellationToken), type, 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(
int? count = null, 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(
Func, IQueryable> query!!, CancellationToken cancellationToken = default)
=> ListAsync(static (authorizations, query) => query(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(
Func, TState, IQueryable> query!!,
TState state, CancellationToken cancellationToken = default)
=> 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(TAuthorization authorization!!,
OpenIddictAuthorizationDescriptor descriptor!!, CancellationToken cancellationToken = default)
{
await Store.SetApplicationIdAsync(authorization, descriptor.ApplicationId, cancellationToken);
await Store.SetCreationDateAsync(authorization, descriptor.CreationDate, cancellationToken);
await Store.SetPropertiesAsync(authorization, descriptor.Properties.ToImmutableDictionary(), cancellationToken);
await Store.SetScopesAsync(authorization, descriptor.Scopes.ToImmutableArray(), 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(
OpenIddictAuthorizationDescriptor descriptor!!,
TAuthorization authorization!!, CancellationToken cancellationToken = default)
{
descriptor.ApplicationId = await Store.GetApplicationIdAsync(authorization, cancellationToken);
descriptor.CreationDate = await Store.GetCreationDateAsync(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);
descriptor.Properties.Clear();
foreach (var pair in await Store.GetPropertiesAsync(authorization, cancellationToken))
{
descriptor.Properties.Add(pair.Key, pair.Value);
}
}
///
/// Removes the authorizations that are marked as invalid and the ad-hoc ones that have no token attached.
/// Only authorizations created before the specified are removed.
///
///
/// To ensure ad-hoc authorizations that no longer have any valid/non-expired token
/// attached are correctly removed, the tokens should always be pruned first.
///
/// The date before which authorizations are not pruned.
/// The that can be used to abort the operation.
///
/// A that can be used to monitor the asynchronous operation.
///
public virtual ValueTask PruneAsync(DateTimeOffset threshold, CancellationToken cancellationToken = default)
=> Store.PruneAsync(threshold, 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(TAuthorization authorization!!, CancellationToken cancellationToken = default)
{
var status = await Store.GetStatusAsync(authorization, cancellationToken);
if (string.Equals(status, Statuses.Revoked, StringComparison.OrdinalIgnoreCase))
{
return true;
}
await Store.SetStatusAsync(authorization, Statuses.Revoked, cancellationToken);
try
{
await UpdateAsync(authorization, cancellationToken);
Logger.LogInformation(SR.GetResourceString(SR.ID6164), await Store.GetIdAsync(authorization, cancellationToken));
return true;
}
catch (ConcurrencyException exception)
{
Logger.LogDebug(exception, SR.GetResourceString(SR.ID6165), await Store.GetIdAsync(authorization, cancellationToken));
return false;
}
catch (Exception exception)
{
Logger.LogWarning(exception, SR.GetResourceString(SR.ID6166), 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(TAuthorization authorization!!, CancellationToken cancellationToken = default)
{
var results = await GetValidationResultsAsync(authorization, cancellationToken);
if (results.Any(result => result != ValidationResult.Success))
{
var builder = new StringBuilder();
builder.AppendLine(SR.GetResourceString(SR.ID0221));
builder.AppendLine();
foreach (var result in results)
{
builder.AppendLine(result.ErrorMessage);
}
throw new OpenIddictExceptions.ValidationException(builder.ToString(), results);
}
await Store.UpdateAsync(authorization, cancellationToken);
if (!Options.CurrentValue.DisableEntityCaching)
{
await Cache.RemoveAsync(authorization, cancellationToken);
await Cache.AddAsync(authorization, cancellationToken);
}
async Task> GetValidationResultsAsync(
TAuthorization authorization, CancellationToken cancellationToken)
{
var builder = ImmutableArray.CreateBuilder();
await foreach (var result in ValidateAsync(authorization, cancellationToken))
{
builder.Add(result);
}
return builder.ToImmutable();
}
}
///
/// 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(TAuthorization authorization!!,
OpenIddictAuthorizationDescriptor descriptor!!, CancellationToken cancellationToken = default)
{
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(
TAuthorization authorization!!, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var type = await Store.GetTypeAsync(authorization, cancellationToken);
if (string.IsNullOrEmpty(type))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2116));
}
else if (!string.Equals(type, AuthorizationTypes.AdHoc, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(type, AuthorizationTypes.Permanent, StringComparison.OrdinalIgnoreCase))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2117));
}
if (string.IsNullOrEmpty(await Store.GetStatusAsync(authorization, cancellationToken)))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2038));
}
// 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(SR.GetResourceString(SR.ID2039));
break;
}
if (scope.Contains(Separators.Space[0]))
{
yield return new ValidationResult(SR.GetResourceString(SR.ID2042));
break;
}
}
}
///
ValueTask IOpenIddictAuthorizationManager.CountAsync(CancellationToken cancellationToken)
=> CountAsync(cancellationToken);
///
ValueTask IOpenIddictAuthorizationManager.CountAsync(Func, IQueryable> query, CancellationToken cancellationToken)
=> CountAsync(query, cancellationToken);
///
async ValueTask