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.
627 lines
28 KiB
627 lines
28 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.Generic;
|
|
using System.Collections.Immutable;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
using System.Text.Encodings.Web;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using JetBrains.Annotations;
|
|
using Microsoft.Extensions.Options;
|
|
using MongoDB.Bson;
|
|
using MongoDB.Driver;
|
|
using MongoDB.Driver.Linq;
|
|
using OpenIddict.Abstractions;
|
|
using OpenIddict.MongoDb.Models;
|
|
|
|
namespace OpenIddict.MongoDb
|
|
{
|
|
/// <summary>
|
|
/// Provides methods allowing to manage the scopes stored in a database.
|
|
/// </summary>
|
|
/// <typeparam name="TScope">The type of the Scope entity.</typeparam>
|
|
public class OpenIddictScopeStore<TScope> : IOpenIddictScopeStore<TScope>
|
|
where TScope : OpenIddictScope
|
|
{
|
|
public OpenIddictScopeStore(
|
|
[NotNull] IOpenIddictMongoDbContext context,
|
|
[NotNull] IOptionsMonitor<OpenIddictMongoDbOptions> options)
|
|
{
|
|
Context = context;
|
|
Options = options;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the database context associated with the current store.
|
|
/// </summary>
|
|
protected IOpenIddictMongoDbContext Context { get; }
|
|
|
|
/// <summary>
|
|
/// Gets the options associated with the current store.
|
|
/// </summary>
|
|
protected IOptionsMonitor<OpenIddictMongoDbOptions> Options { 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="ValueTask"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the number of scopes in the database.
|
|
/// </returns>
|
|
public virtual async ValueTask<long> CountAsync(CancellationToken cancellationToken)
|
|
{
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.CurrentValue.ScopesCollectionName);
|
|
|
|
return await collection.CountDocumentsAsync(FilterDefinition<TScope>.Empty, null, 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="ValueTask"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the number of scopes that match the specified query.
|
|
/// </returns>
|
|
public virtual async ValueTask<long> CountAsync<TResult>(
|
|
[NotNull] Func<IQueryable<TScope>, IQueryable<TResult>> query, CancellationToken cancellationToken)
|
|
{
|
|
if (query == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(query));
|
|
}
|
|
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.CurrentValue.ScopesCollectionName);
|
|
|
|
return await ((IMongoQueryable<TScope>) query(collection.AsQueryable())).LongCountAsync(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="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
|
|
public virtual async ValueTask CreateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.CurrentValue.ScopesCollectionName);
|
|
|
|
await collection.InsertOneAsync(scope, null, cancellationToken);
|
|
}
|
|
|
|
/// <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="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
|
|
public virtual async ValueTask DeleteAsync([NotNull] TScope scope, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.CurrentValue.ScopesCollectionName);
|
|
|
|
if ((await collection.DeleteOneAsync(entity =>
|
|
entity.Id == scope.Id &&
|
|
entity.ConcurrencyToken == scope.ConcurrencyToken)).DeletedCount == 0)
|
|
{
|
|
throw new OpenIddictExceptions.ConcurrencyException(new StringBuilder()
|
|
.AppendLine("The scope was concurrently updated and cannot be persisted in its current state.")
|
|
.Append("Reload the scope from the database and retry the operation.")
|
|
.ToString());
|
|
}
|
|
}
|
|
|
|
/// <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="ValueTask"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the scope corresponding to the identifier.
|
|
/// </returns>
|
|
public virtual async ValueTask<TScope> FindByIdAsync([NotNull] string identifier, CancellationToken cancellationToken)
|
|
{
|
|
if (string.IsNullOrEmpty(identifier))
|
|
{
|
|
throw new ArgumentException("The identifier cannot be null or empty.", nameof(identifier));
|
|
}
|
|
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.CurrentValue.ScopesCollectionName);
|
|
|
|
return await collection.Find(scope => scope.Id == ObjectId.Parse(identifier)).FirstOrDefaultAsync(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="ValueTask"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the scope corresponding to the specified name.
|
|
/// </returns>
|
|
public virtual async ValueTask<TScope> FindByNameAsync([NotNull] string name, CancellationToken cancellationToken)
|
|
{
|
|
if (string.IsNullOrEmpty(name))
|
|
{
|
|
throw new ArgumentException("The scope name cannot be null or empty.", nameof(name));
|
|
}
|
|
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.CurrentValue.ScopesCollectionName);
|
|
|
|
return await collection.Find(scope => scope.Name == name).FirstOrDefaultAsync(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>The scopes corresponding to the specified names.</returns>
|
|
public virtual IAsyncEnumerable<TScope> FindByNamesAsync(
|
|
ImmutableArray<string> names, CancellationToken cancellationToken)
|
|
{
|
|
if (names.Any(name => string.IsNullOrEmpty(name)))
|
|
{
|
|
throw new ArgumentException("Scope names cannot be null or empty.", nameof(names));
|
|
}
|
|
|
|
return ExecuteAsync(cancellationToken);
|
|
|
|
async IAsyncEnumerable<TScope> ExecuteAsync(CancellationToken cancellationToken)
|
|
{
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.CurrentValue.ScopesCollectionName);
|
|
|
|
// Note: Enumerable.Contains() is deliberately used without the extension method syntax to ensure
|
|
// ImmutableArray.Contains() (which is not fully supported by MongoDB) is not used instead.
|
|
await foreach (var scope in collection.Find(scope => Enumerable.Contains(names, scope.Name)).ToAsyncEnumerable(cancellationToken))
|
|
{
|
|
yield return scope;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves all the scopes that contain the specified resource.
|
|
/// </summary>
|
|
/// <param name="resource">The resource associated with the scopes.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>The scopes associated with the specified resource.</returns>
|
|
public virtual IAsyncEnumerable<TScope> FindByResourceAsync(
|
|
[NotNull] string resource, CancellationToken cancellationToken)
|
|
{
|
|
if (string.IsNullOrEmpty(resource))
|
|
{
|
|
throw new ArgumentException("The resource cannot be null or empty.", nameof(resource));
|
|
}
|
|
|
|
return ExecuteAsync(cancellationToken);
|
|
|
|
async IAsyncEnumerable<TScope> ExecuteAsync(CancellationToken cancellationToken)
|
|
{
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.CurrentValue.ScopesCollectionName);
|
|
|
|
await foreach (var scope in collection.Find(scope => scope.Resources.Contains(resource)).ToAsyncEnumerable(cancellationToken))
|
|
{
|
|
yield return scope;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <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="ValueTask"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the first element returned when executing the query.
|
|
/// </returns>
|
|
public virtual async ValueTask<TResult> GetAsync<TState, TResult>(
|
|
[NotNull] Func<IQueryable<TScope>, TState, IQueryable<TResult>> query,
|
|
[CanBeNull] TState state, CancellationToken cancellationToken)
|
|
{
|
|
if (query == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(query));
|
|
}
|
|
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.CurrentValue.ScopesCollectionName);
|
|
|
|
return await ((IMongoQueryable<TResult>) query(collection.AsQueryable(), state)).FirstOrDefaultAsync(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="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the description associated with the specified scope.
|
|
/// </returns>
|
|
public virtual ValueTask<string> GetDescriptionAsync([NotNull] TScope scope, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
return new ValueTask<string>(scope.Description);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the display 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="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the display name associated with the scope.
|
|
/// </returns>
|
|
public virtual ValueTask<string> GetDisplayNameAsync([NotNull] TScope scope, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
return new ValueTask<string>(scope.DisplayName);
|
|
}
|
|
|
|
/// <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="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the unique identifier associated with the scope.
|
|
/// </returns>
|
|
public virtual ValueTask<string> GetIdAsync([NotNull] TScope scope, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
return new ValueTask<string>(scope.Id.ToString());
|
|
}
|
|
|
|
/// <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="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the name associated with the specified scope.
|
|
/// </returns>
|
|
public virtual ValueTask<string> GetNameAsync([NotNull] TScope scope, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
return new ValueTask<string>(scope.Name);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the additional properties 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="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns all the additional properties associated with the scope.
|
|
/// </returns>
|
|
public virtual ValueTask<ImmutableDictionary<string, JsonElement>> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
if (scope.Properties == null)
|
|
{
|
|
return new ValueTask<ImmutableDictionary<string, JsonElement>>(ImmutableDictionary.Create<string, JsonElement>());
|
|
}
|
|
|
|
return new ValueTask<ImmutableDictionary<string, JsonElement>>(
|
|
JsonSerializer.Deserialize<ImmutableDictionary<string, JsonElement>>(scope.Properties.ToJson()));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves the resources 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="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns all the resources associated with the scope.
|
|
/// </returns>
|
|
public virtual ValueTask<ImmutableArray<string>> GetResourcesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
if (scope.Resources == null || scope.Resources.Length == 0)
|
|
{
|
|
return new ValueTask<ImmutableArray<string>>(ImmutableArray.Create<string>());
|
|
}
|
|
|
|
return new ValueTask<ImmutableArray<string>>(scope.Resources.ToImmutableArray());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Instantiates a new scope.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>
|
|
/// A <see cref="ValueTask{TResult}"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the instantiated scope, that can be persisted in the database.
|
|
/// </returns>
|
|
public virtual ValueTask<TScope> InstantiateAsync(CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
return new ValueTask<TScope>(Activator.CreateInstance<TScope>());
|
|
}
|
|
|
|
catch (MemberAccessException exception)
|
|
{
|
|
return new ValueTask<TScope>(Task.FromException<TScope>(
|
|
new InvalidOperationException(new StringBuilder()
|
|
.AppendLine("An error occurred while trying to create a new scope instance.")
|
|
.Append("Make sure that the scope entity is not abstract and has a public parameterless constructor ")
|
|
.Append("or create a custom scope store that overrides 'InstantiateAsync()' to use a custom factory.")
|
|
.ToString(), exception)));
|
|
}
|
|
}
|
|
|
|
/// <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>All the elements returned when executing the specified query.</returns>
|
|
public virtual async IAsyncEnumerable<TScope> ListAsync(
|
|
[CanBeNull] int? count, [CanBeNull] int? offset, [EnumeratorCancellation] CancellationToken cancellationToken)
|
|
{
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.CurrentValue.ScopesCollectionName);
|
|
|
|
var query = (IMongoQueryable<TScope>) collection.AsQueryable().OrderBy(scope => scope.Id);
|
|
|
|
if (offset.HasValue)
|
|
{
|
|
query = query.Skip(offset.Value);
|
|
}
|
|
|
|
if (count.HasValue)
|
|
{
|
|
query = query.Take(count.Value);
|
|
}
|
|
|
|
await foreach (var scope in ((IAsyncCursorSource<TScope>) query).ToAsyncEnumerable(cancellationToken))
|
|
{
|
|
yield return scope;
|
|
}
|
|
}
|
|
|
|
/// <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>All the elements returned when executing the specified query.</returns>
|
|
public virtual IAsyncEnumerable<TResult> ListAsync<TState, TResult>(
|
|
[NotNull] Func<IQueryable<TScope>, TState, IQueryable<TResult>> query,
|
|
[CanBeNull] TState state, CancellationToken cancellationToken)
|
|
{
|
|
if (query == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(query));
|
|
}
|
|
|
|
return ExecuteAsync(cancellationToken);
|
|
|
|
async IAsyncEnumerable<TResult> ExecuteAsync(CancellationToken cancellationToken)
|
|
{
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.CurrentValue.ScopesCollectionName);
|
|
|
|
await foreach (var element in query(collection.AsQueryable(), state).ToAsyncEnumerable(cancellationToken))
|
|
{
|
|
yield return element;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the description associated with a scope.
|
|
/// </summary>
|
|
/// <param name="scope">The scope.</param>
|
|
/// <param name="description">The description associated with the authorization.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
|
|
public virtual ValueTask SetDescriptionAsync([NotNull] TScope scope, [CanBeNull] string description, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
scope.Description = description;
|
|
|
|
return default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the display name associated with a scope.
|
|
/// </summary>
|
|
/// <param name="scope">The scope.</param>
|
|
/// <param name="name">The display 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="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
|
|
public virtual ValueTask SetDisplayNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
scope.DisplayName = name;
|
|
|
|
return default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the name associated with a scope.
|
|
/// </summary>
|
|
/// <param name="scope">The scope.</param>
|
|
/// <param name="name">The name associated with the authorization.</param>
|
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
|
/// <returns>A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
|
|
public virtual ValueTask SetNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
scope.Name = name;
|
|
|
|
return default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the additional properties associated with a scope.
|
|
/// </summary>
|
|
/// <param name="scope">The scope.</param>
|
|
/// <param name="properties">The additional properties 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="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
|
|
public virtual ValueTask SetPropertiesAsync([NotNull] TScope scope,
|
|
[CanBeNull] ImmutableDictionary<string, JsonElement> properties, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
if (properties == null || properties.IsEmpty)
|
|
{
|
|
scope.Properties = null;
|
|
|
|
return default;
|
|
}
|
|
|
|
scope.Properties = BsonDocument.Parse(JsonSerializer.Serialize(properties, new JsonSerializerOptions
|
|
{
|
|
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
|
WriteIndented = false
|
|
}));
|
|
|
|
return default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the resources associated with a scope.
|
|
/// </summary>
|
|
/// <param name="scope">The scope.</param>
|
|
/// <param name="resources">The resources 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="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
|
|
public virtual ValueTask SetResourcesAsync([NotNull] TScope scope, ImmutableArray<string> resources, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
if (resources.IsDefaultOrEmpty)
|
|
{
|
|
scope.Resources = null;
|
|
|
|
return default;
|
|
}
|
|
|
|
scope.Resources = resources.ToArray();
|
|
|
|
return default;
|
|
}
|
|
|
|
/// <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="ValueTask"/> that can be used to monitor the asynchronous operation.</returns>
|
|
public virtual async ValueTask UpdateAsync([NotNull] TScope scope, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
// Generate a new concurrency token and attach it
|
|
// to the scope before persisting the changes.
|
|
var timestamp = scope.ConcurrencyToken;
|
|
scope.ConcurrencyToken = Guid.NewGuid().ToString();
|
|
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.CurrentValue.ScopesCollectionName);
|
|
|
|
if ((await collection.ReplaceOneAsync(entity =>
|
|
entity.Id == scope.Id &&
|
|
entity.ConcurrencyToken == timestamp, scope, null, cancellationToken)).MatchedCount == 0)
|
|
{
|
|
throw new OpenIddictExceptions.ConcurrencyException(new StringBuilder()
|
|
.AppendLine("The scope was concurrently updated and cannot be persisted in its current state.")
|
|
.Append("Reload the scope from the database and retry the operation.")
|
|
.ToString());
|
|
}
|
|
}
|
|
}
|
|
}
|