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.
622 lines
28 KiB
622 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.Text;
|
|
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 Newtonsoft.Json.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] IOptions<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 IOptions<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="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the number of scopes in the database.
|
|
/// </returns>
|
|
public virtual async Task<long> CountAsync(CancellationToken cancellationToken)
|
|
{
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.Value.ScopesCollectionName);
|
|
|
|
return await collection.CountDocumentsAsync(FilterDefinition<TScope>.Empty);
|
|
}
|
|
|
|
/// <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 async Task<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.Value.ScopesCollectionName);
|
|
|
|
return await ((IMongoQueryable<TScope>) query(collection.AsQueryable())).LongCountAsync();
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.Value.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="Task"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public virtual async Task 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.Value.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="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the scope corresponding to the identifier.
|
|
/// </returns>
|
|
public virtual async Task<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.Value.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="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the scope corresponding to the specified name.
|
|
/// </returns>
|
|
public virtual async Task<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.Value.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>
|
|
/// 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 async Task<ImmutableArray<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));
|
|
}
|
|
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.Value.ScopesCollectionName);
|
|
|
|
return ImmutableArray.CreateRange(await collection.Find(scope => names.Contains(scope.Name)).ToListAsync(cancellationToken));
|
|
}
|
|
|
|
/// <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>
|
|
/// A <see cref="Task"/> that can be used to monitor the asynchronous operation,
|
|
/// whose result returns the scopes associated with the specified resource.
|
|
/// </returns>
|
|
public virtual async Task<ImmutableArray<TScope>> FindByResourceAsync(
|
|
[NotNull] string resource, CancellationToken cancellationToken)
|
|
{
|
|
if (string.IsNullOrEmpty(resource))
|
|
{
|
|
throw new ArgumentException("The resource cannot be null or empty.", nameof(resource));
|
|
}
|
|
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.Value.ScopesCollectionName);
|
|
|
|
return ImmutableArray.CreateRange(await collection.Find(scope =>
|
|
scope.Resources.Contains(resource)).ToListAsync(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 async Task<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.Value.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<JObject> GetPropertiesAsync([NotNull] TScope scope, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
if (scope.Properties == null)
|
|
{
|
|
return new ValueTask<JObject>(new JObject());
|
|
}
|
|
|
|
return new ValueTask<JObject>(JObject.FromObject(scope.Properties.ToDictionary()));
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
var source = new TaskCompletionSource<TScope>();
|
|
source.SetException(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));
|
|
|
|
return new ValueTask<TScope>(source.Task);
|
|
}
|
|
}
|
|
|
|
/// <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 async Task<ImmutableArray<TScope>> ListAsync(
|
|
[CanBeNull] int? count, [CanBeNull] int? offset, CancellationToken cancellationToken)
|
|
{
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.Value.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);
|
|
}
|
|
|
|
return ImmutableArray.CreateRange(await query.ToListAsync(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 async Task<ImmutableArray<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));
|
|
}
|
|
|
|
var database = await Context.GetDatabaseAsync(cancellationToken);
|
|
var collection = database.GetCollection<TScope>(Options.Value.ScopesCollectionName);
|
|
|
|
return ImmutableArray.CreateRange(
|
|
await ((IMongoQueryable<TResult>) query(collection.AsQueryable(), state)).ToListAsync(cancellationToken));
|
|
}
|
|
|
|
/// <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="Task"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public virtual Task SetDescriptionAsync([NotNull] TScope scope, [CanBeNull] string description, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
scope.Description = description;
|
|
|
|
return Task.FromResult(0);
|
|
}
|
|
|
|
/// <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="Task"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public virtual Task SetDisplayNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
scope.DisplayName = name;
|
|
|
|
return Task.FromResult(0);
|
|
}
|
|
|
|
/// <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="Task"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public virtual Task SetNameAsync([NotNull] TScope scope, [CanBeNull] string name, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
scope.Name = name;
|
|
|
|
return Task.FromResult(0);
|
|
}
|
|
|
|
/// <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="Task"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public virtual Task SetPropertiesAsync([NotNull] TScope scope, [CanBeNull] JObject properties, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
if (properties == null)
|
|
{
|
|
scope.Properties = null;
|
|
|
|
return Task.FromResult(0);
|
|
}
|
|
|
|
scope.Properties = new BsonDocument(properties.ToObject<IDictionary<string, object>>());
|
|
|
|
return Task.FromResult(0);
|
|
}
|
|
|
|
/// <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="Task"/> that can be used to monitor the asynchronous operation.
|
|
/// </returns>
|
|
public virtual Task SetResourcesAsync([NotNull] TScope scope, ImmutableArray<string> resources, CancellationToken cancellationToken)
|
|
{
|
|
if (scope == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(scope));
|
|
}
|
|
|
|
if (resources.IsDefaultOrEmpty)
|
|
{
|
|
scope.Resources = null;
|
|
|
|
return Task.FromResult(0);
|
|
}
|
|
|
|
scope.Resources = resources.ToArray();
|
|
|
|
return Task.FromResult(0);
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
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.Value.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());
|
|
}
|
|
}
|
|
}
|
|
}
|