You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
490 lines
22 KiB
490 lines
22 KiB
/*
|
|
* 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.Threading;
|
|
using System.Threading.Tasks;
|
|
using JetBrains.Annotations;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace OpenIddict.Core
|
|
{
|
|
/// <summary>
|
|
/// Provides methods allowing to manage the scopes stored in the store.
|
|
/// </summary>
|
|
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
|
|
public class OpenIddictScopeManager<TScope> where TScope : class
|
|
{
|
|
public OpenIddictScopeManager(
|
|
[NotNull] IOpenIddictScopeStore<TScope> store,
|
|
[NotNull] ILogger<OpenIddictScopeManager<TScope>> logger)
|
|
{
|
|
Logger = logger;
|
|
Store = store;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the logger associated with the current manager.
|
|
/// </summary>
|
|
protected ILogger Logger { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the store associated with the current manager.
|
|
/// </summary>
|
|
protected IOpenIddictScopeStore<TScope> Store { get; }
|
|
|
|
/// <summary>
|
|
/// Determines the number of scopes that exist in the database.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the number of scopes in the database.
|
|
/// </returns>
|
|
public virtual Task<long> CountAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
return Store.CountAsync(cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines the number of scopes that match the specified query.
|
|
/// </summary>
|
|
/// <typeparam name="TResult">The result type.</typeparam>
|
|
/// <param name="query">The query to execute.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the number of scopes that match the specified query.
|
|
/// </returns>
|
|
public virtual Task<long> CountAsync<TResult>(
|
|
[NotNull] Func<IQueryable<TScope>, IQueryable<TResult>> query, CancellationToken cancellationToken = default)
|
|
{
|
|
if (query == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(query));
|
|
}
|
|
|
|
return Store.CountAsync(query, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new scope.
|
|
/// </summary>
|
|
/// <param name="scope">The scope to create.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public virtual async Task CreateAsync([NotNull] TScope scope, CancellationToken cancellationToken = default)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
var results = await ValidateAsync(scope, cancellationToken);
|
|
if (results.Any(result => result != ValidationResult.Success))
|
|
{
|
|
throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, scope);
|
|
}
|
|
|
|
await Store.CreateAsync(scope, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new scope based on the specified descriptor.
|
|
/// </summary>
|
|
/// <param name="descriptor">The scope descriptor.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation, whose result returns the scope.
|
|
/// </returns>
|
|
public virtual async Task<TScope> CreateAsync(
|
|
[NotNull] OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default)
|
|
{
|
|
if (descriptor == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(descriptor));
|
|
}
|
|
|
|
var scope = await Store.InstantiateAsync(cancellationToken);
|
|
if (scope == null)
|
|
{
|
|
throw new InvalidOperationException("An error occurred while trying to create a new scope.");
|
|
}
|
|
|
|
await PopulateAsync(scope, descriptor, cancellationToken);
|
|
await CreateAsync(scope, cancellationToken);
|
|
|
|
return scope;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes an existing scope.
|
|
/// </summary>
|
|
/// <param name="scope">The scope to delete.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public virtual Task DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken = default)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
return Store.DeleteAsync(scope, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a scope using its unique identifier.
|
|
/// </summary>
|
|
/// <param name="identifier">The unique identifier associated with the scope.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the scope corresponding to the identifier.
|
|
/// </returns>
|
|
public virtual Task<TScope> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a scope using its name.
|
|
/// </summary>
|
|
/// <param name="name">The name associated with the scope.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the scope corresponding to the specified name.
|
|
/// </returns>
|
|
public virtual Task<TScope> FindByNameAsync([NotNull] string name, CancellationToken cancellationToken = default)
|
|
{
|
|
if (string.IsNullOrEmpty(name))
|
|
{
|
|
throw new ArgumentException("The scope name cannot be null or empty.", nameof(name));
|
|
}
|
|
|
|
return Store.FindByNameAsync(name, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a list of scopes using their name.
|
|
/// </summary>
|
|
/// <param name="names">The names associated with the scopes.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the scopes corresponding to the specified names.
|
|
/// </returns>
|
|
public virtual Task<ImmutableArray<TScope>> FindByNamesAsync(
|
|
ImmutableArray<string> names, CancellationToken cancellationToken = default)
|
|
{
|
|
if (names.Any(name => string.IsNullOrEmpty(name)))
|
|
{
|
|
throw new ArgumentException("Scope names cannot be null or empty.", nameof(names));
|
|
}
|
|
|
|
return Store.FindByNamesAsync(names, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes the specified query and returns the first element.
|
|
/// </summary>
|
|
/// <typeparam name="TResult">The result type.</typeparam>
|
|
/// <param name="query">The query to execute.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the first element returned when executing the query.
|
|
/// </returns>
|
|
public virtual Task<TResult> GetAsync<TResult>(
|
|
[NotNull] Func<IQueryable<TScope>, IQueryable<TResult>> query, CancellationToken cancellationToken = default)
|
|
{
|
|
return GetAsync((scopes, state) => state(scopes), query, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes the specified query and returns the first element.
|
|
/// </summary>
|
|
/// <typeparam name="TState">The state type.</typeparam>
|
|
/// <typeparam name="TResult">The result type.</typeparam>
|
|
/// <param name="query">The query to execute.</param>
|
|
/// <param name="state">The optional state.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the first element returned when executing the query.
|
|
/// </returns>
|
|
public virtual Task<TResult> GetAsync<TState, TResult>(
|
|
[NotNull] Func<IQueryable<TScope>, TState, IQueryable<TResult>> query,
|
|
[CanBeNull] TState state, CancellationToken cancellationToken = default)
|
|
{
|
|
if (query == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(query));
|
|
}
|
|
|
|
return Store.GetAsync(query, state, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the description associated with a scope.
|
|
/// </summary>
|
|
/// <param name="scope">The scope.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the description associated with the specified scope.
|
|
/// </returns>
|
|
public virtual Task<string> GetDescriptionAsync([NotNull] TScope scope, CancellationToken cancellationToken = default)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
return Store.GetDescriptionAsync(scope, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the unique identifier associated with a scope.
|
|
/// </summary>
|
|
/// <param name="scope">The scope.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the unique identifier associated with the scope.
|
|
/// </returns>
|
|
public virtual Task<string> GetIdAsync([NotNull] TScope scope, CancellationToken cancellationToken = default)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
return Store.GetIdAsync(scope, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the name associated with a scope.
|
|
/// </summary>
|
|
/// <param name="scope">The scope.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the name associated with the specified scope.
|
|
/// </returns>
|
|
public virtual Task<string> GetNameAsync([NotNull] TScope scope, CancellationToken cancellationToken = default)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
return Store.GetIdAsync(scope, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes the specified query and returns all the corresponding elements.
|
|
/// </summary>
|
|
/// <param name="count">The number of results to return.</param>
|
|
/// <param name="offset">The number of results to skip.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns all the elements returned when executing the specified query.
|
|
/// </returns>
|
|
public virtual Task<ImmutableArray<TScope>> ListAsync(
|
|
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken = default)
|
|
{
|
|
return Store.ListAsync(count, offset, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes the specified query and returns all the corresponding elements.
|
|
/// </summary>
|
|
/// <typeparam name="TResult">The result type.</typeparam>
|
|
/// <param name="query">The query to execute.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns all the elements returned when executing the specified query.
|
|
/// </returns>
|
|
public virtual Task<ImmutableArray<TResult>> ListAsync<TResult>(
|
|
[NotNull] Func<IQueryable<TScope>, IQueryable<TResult>> query, CancellationToken cancellationToken = default)
|
|
{
|
|
return ListAsync((scopes, state) => state(scopes), query, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes the specified query and returns all the corresponding elements.
|
|
/// </summary>
|
|
/// <typeparam name="TState">The state type.</typeparam>
|
|
/// <typeparam name="TResult">The result type.</typeparam>
|
|
/// <param name="query">The query to execute.</param>
|
|
/// <param name="state">The optional state.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns all the elements returned when executing the specified query.
|
|
/// </returns>
|
|
public virtual Task<ImmutableArray<TResult>> ListAsync<TState, TResult>(
|
|
[NotNull] Func<IQueryable<TScope>, TState, IQueryable<TResult>> query,
|
|
[CanBeNull] TState state, CancellationToken cancellationToken = default)
|
|
{
|
|
if (query == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(query));
|
|
}
|
|
|
|
return Store.ListAsync(query, state, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates an existing scope.
|
|
/// </summary>
|
|
/// <param name="scope">The scope to update.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public virtual async Task UpdateAsync([NotNull] TScope scope, CancellationToken cancellationToken = default)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
var results = await ValidateAsync(scope, cancellationToken);
|
|
if (results.Any(result => result != ValidationResult.Success))
|
|
{
|
|
throw new ValidationException(results.FirstOrDefault(result => result != ValidationResult.Success), null, scope);
|
|
}
|
|
|
|
await Store.UpdateAsync(scope, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates an existing scope.
|
|
/// </summary>
|
|
/// <param name="scope">The scope to update.</param>
|
|
/// <param name="operation">The delegate used to update the scope based on the given descriptor.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public virtual async Task UpdateAsync([NotNull] TScope scope,
|
|
[NotNull] Func<OpenIddictScopeDescriptor, Task> operation, CancellationToken cancellationToken = default)
|
|
{
|
|
if (operation == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(operation));
|
|
}
|
|
|
|
var descriptor = new OpenIddictScopeDescriptor
|
|
{
|
|
Description = await Store.GetDescriptionAsync(scope, cancellationToken),
|
|
Name = await Store.GetNameAsync(scope, cancellationToken)
|
|
};
|
|
|
|
await operation(descriptor);
|
|
await PopulateAsync(scope, descriptor, cancellationToken);
|
|
await UpdateAsync(scope, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates the scope to ensure it's in a consistent state.
|
|
/// </summary>
|
|
/// <param name="scope">The scope.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the validation error encountered when validating the scope.
|
|
/// </returns>
|
|
public virtual async Task<ImmutableArray<ValidationResult>> ValidateAsync(
|
|
[NotNull] TScope scope, CancellationToken cancellationToken = default)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
var results = ImmutableArray.CreateBuilder<ValidationResult>();
|
|
|
|
if (string.IsNullOrEmpty(await Store.GetNameAsync(scope, cancellationToken)))
|
|
{
|
|
results.Add(new ValidationResult("The scope name cannot be null or empty."));
|
|
}
|
|
|
|
return results.ToImmutable();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates the list of scopes to ensure they correspond to existing elements in the database.
|
|
/// </summary>
|
|
/// <param name="scopes">The scopes.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns><c>true</c> if the list of scopes is valid, <c>false</c> otherwise.</returns>
|
|
public virtual async Task<bool> ValidateScopesAsync(ImmutableArray<string> scopes, CancellationToken cancellationToken = default)
|
|
{
|
|
if (scopes.Length == 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
async Task<ImmutableHashSet<string>> GetScopesAsync()
|
|
{
|
|
var names = ImmutableHashSet.CreateBuilder(StringComparer.Ordinal);
|
|
|
|
foreach (var scope in await FindByNamesAsync(scopes, cancellationToken))
|
|
{
|
|
names.Add(await GetNameAsync(scope, cancellationToken));
|
|
}
|
|
|
|
return names.ToImmutable();
|
|
}
|
|
|
|
return (await GetScopesAsync()).IsSupersetOf(scopes);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Populates the scope using the specified descriptor.
|
|
/// </summary>
|
|
/// <param name="scope">The scope.</param>
|
|
/// <param name="descriptor">The descriptor.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
protected virtual async Task PopulateAsync([NotNull] TScope scope,
|
|
[NotNull] OpenIddictScopeDescriptor descriptor, CancellationToken cancellationToken = default)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
if (descriptor == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(descriptor));
|
|
}
|
|
|
|
await Store.SetDescriptionAsync(scope, descriptor.Description, cancellationToken);
|
|
await Store.SetNameAsync(scope, descriptor.Description, cancellationToken);
|
|
}
|
|
}
|
|
}
|