Browse Source

Introduce OpenIddictScopeManager.FindByResourceAsync() to allow retrieving all the scopes associated with a given resource

pull/602/head
Kévin Chalet 8 years ago
parent
commit
6533383771
  1. 11
      src/OpenIddict.Abstractions/Managers/IOpenIddictScopeManager.cs
  2. 11
      src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs
  3. 48
      src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs
  4. 46
      src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs
  5. 9
      src/OpenIddict.Stores/Stores/OpenIddictApplicationStore.cs
  6. 44
      src/OpenIddict.Stores/Stores/OpenIddictScopeStore.cs

11
src/OpenIddict.Abstractions/Managers/IOpenIddictScopeManager.cs

@ -103,6 +103,17 @@ namespace OpenIddict.Abstractions
/// </returns> /// </returns>
Task<ImmutableArray<object>> FindByNamesAsync(ImmutableArray<string> names, CancellationToken cancellationToken = default); Task<ImmutableArray<object>> FindByNamesAsync(ImmutableArray<string> names, CancellationToken cancellationToken = default);
/// <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>
Task<ImmutableArray<object>> FindByResourceAsync([NotNull] string resource, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Executes the specified query and returns the first element. /// Executes the specified query and returns the first element.
/// </summary> /// </summary>

11
src/OpenIddict.Abstractions/Stores/IOpenIddictScopeStore.cs

@ -95,6 +95,17 @@ namespace OpenIddict.Abstractions
/// </returns> /// </returns>
Task<ImmutableArray<TScope>> FindByNamesAsync(ImmutableArray<string> names, CancellationToken cancellationToken); Task<ImmutableArray<TScope>> FindByNamesAsync(ImmutableArray<string> names, CancellationToken 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>
Task<ImmutableArray<TScope>> FindByResourceAsync([NotNull] string resource, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Executes the specified query and returns the first element. /// Executes the specified query and returns the first element.
/// </summary> /// </summary>

48
src/OpenIddict.Core/Managers/OpenIddictScopeManager.cs

@ -241,6 +241,51 @@ namespace OpenIddict.Core
builder.ToImmutable(); builder.ToImmutable();
} }
/// <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 = default)
{
if (string.IsNullOrEmpty(resource))
{
throw new ArgumentException("The resource cannot be null or empty.", nameof(resource));
}
// SQL engines like Microsoft SQL Server or MySQL are known to use case-insensitive lookups by default.
// To ensure a case-sensitive comparison is enforced independently of the database/table/query collation
// used by the store, a second pass using string.Equals(StringComparison.Ordinal) is manually made here.
var scopes = await Store.FindByResourceAsync(resource, cancellationToken);
if (scopes.IsEmpty)
{
return ImmutableArray.Create<TScope>();
}
var builder = ImmutableArray.CreateBuilder<TScope>(scopes.Length);
foreach (var scope in scopes)
{
foreach (var value in await Store.GetResourcesAsync(scope, cancellationToken))
{
if (string.Equals(value, resource, StringComparison.Ordinal))
{
builder.Add(scope);
}
}
}
return builder.Count == builder.Capacity ?
builder.MoveToImmutable() :
builder.ToImmutable();
}
/// <summary> /// <summary>
/// Executes the specified query and returns the first element. /// Executes the specified query and returns the first element.
/// </summary> /// </summary>
@ -650,6 +695,9 @@ namespace OpenIddict.Core
async Task<ImmutableArray<object>> IOpenIddictScopeManager.FindByNamesAsync(ImmutableArray<string> names, CancellationToken cancellationToken) async Task<ImmutableArray<object>> IOpenIddictScopeManager.FindByNamesAsync(ImmutableArray<string> names, CancellationToken cancellationToken)
=> (await FindByNamesAsync(names, cancellationToken)).CastArray<object>(); => (await FindByNamesAsync(names, cancellationToken)).CastArray<object>();
async Task<ImmutableArray<object>> IOpenIddictScopeManager.FindByResourceAsync(string resource, CancellationToken cancellationToken)
=> (await FindByResourceAsync(resource, cancellationToken)).CastArray<object>();
Task<TResult> IOpenIddictScopeManager.GetAsync<TResult>(Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken) Task<TResult> IOpenIddictScopeManager.GetAsync<TResult>(Func<IQueryable<object>, IQueryable<TResult>> query, CancellationToken cancellationToken)
=> GetAsync(query, cancellationToken); => GetAsync(query, cancellationToken);

46
src/OpenIddict.EntityFrameworkCore/Stores/OpenIddictScopeStore.cs

@ -227,6 +227,52 @@ namespace OpenIddict.EntityFrameworkCore
return ImmutableArray.CreateRange(await query(Context, names).ToListAsync(cancellationToken)); return ImmutableArray.CreateRange(await query(Context, names).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 override 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));
}
// To optimize the efficiency of the query a bit, only scopes whose stringified
// Resources column contains the specified resource are returned. Once the scopes
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
var query = Cache.GetOrCreate("45bae754-72fc-422c-b3a2-90867600a029", entry =>
{
entry.SetPriority(CacheItemPriority.NeverRemove);
return EF.CompileAsyncQuery((TContext context, string value) =>
from scope in context.Set<TScope>().AsTracking()
where scope.Resources.Contains(value)
select scope);
});
var builder = ImmutableArray.CreateBuilder<TScope>();
foreach (var scope in await query(Context, resource).ToListAsync(cancellationToken))
{
var resources = await GetResourcesAsync(scope, cancellationToken);
if (resources.Contains(resource, StringComparer.Ordinal))
{
builder.Add(scope);
}
}
return builder.ToImmutable();
}
/// <summary> /// <summary>
/// Executes the specified query and returns the first element. /// Executes the specified query and returns the first element.
/// </summary> /// </summary>

9
src/OpenIddict.Stores/Stores/OpenIddictApplicationStore.cs

@ -392,7 +392,8 @@ namespace OpenIddict.Stores
// Note: parsing the stringified permissions is an expensive operation. // Note: parsing the stringified permissions is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache. // To mitigate that, the resulting array is stored in the memory cache.
var permissions = Cache.GetOrCreate("0347e0aa-3a26-410a-97e8-a83bdeb21a1f", entry => var key = string.Concat("0347e0aa-3a26-410a-97e8-a83bdeb21a1f", "\x1e", application.Permissions);
var permissions = Cache.GetOrCreate(key, entry =>
{ {
entry.SetPriority(CacheItemPriority.High) entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1)); .SetSlidingExpiration(TimeSpan.FromMinutes(1));
@ -428,7 +429,8 @@ namespace OpenIddict.Stores
// Note: parsing the stringified addresses is an expensive operation. // Note: parsing the stringified addresses is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache. // To mitigate that, the resulting array is stored in the memory cache.
var addresses = Cache.GetOrCreate("fb14dfb9-9216-4b77-bfa9-7e85f8201ff4", entry => var key = string.Concat("fb14dfb9-9216-4b77-bfa9-7e85f8201ff4", "\x1e", application.PostLogoutRedirectUris);
var addresses = Cache.GetOrCreate(key, entry =>
{ {
entry.SetPriority(CacheItemPriority.High) entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1)); .SetSlidingExpiration(TimeSpan.FromMinutes(1));
@ -488,7 +490,8 @@ namespace OpenIddict.Stores
// Note: parsing the stringified addresses is an expensive operation. // Note: parsing the stringified addresses is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache. // To mitigate that, the resulting array is stored in the memory cache.
var addresses = Cache.GetOrCreate("851d6f08-2ee0-4452-bbe5-ab864611ecaa", entry => var key = string.Concat("851d6f08-2ee0-4452-bbe5-ab864611ecaa", "\x1e", application.RedirectUris);
var addresses = Cache.GetOrCreate(key, entry =>
{ {
entry.SetPriority(CacheItemPriority.High) entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1)); .SetSlidingExpiration(TimeSpan.FromMinutes(1));

44
src/OpenIddict.Stores/Stores/OpenIddictScopeStore.cs

@ -162,6 +162,47 @@ namespace OpenIddict.Stores
return ListAsync((scopes, values) => Query(scopes, values), names.ToArray(), cancellationToken); return ListAsync((scopes, values) => Query(scopes, values), names.ToArray(), 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));
}
// To optimize the efficiency of the query a bit, only scopes whose stringified
// Resources column contains the specified resource are returned. Once the scopes
// are retrieved, a second pass is made to ensure only valid elements are returned.
// Implementers that use this method in a hot path may want to override this method
// to use SQL Server 2016 functions like JSON_VALUE to make the query more efficient.
IQueryable<TScope> Query(IQueryable<TScope> scopes, string state)
=> from scope in scopes
where scope.Resources.Contains(state)
select scope;
var builder = ImmutableArray.CreateBuilder<TScope>();
foreach (var application in await ListAsync((applications, state) => Query(applications, state), resource, cancellationToken))
{
var resources = await GetResourcesAsync(application, cancellationToken);
if (resources.Contains(resource, StringComparer.OrdinalIgnoreCase))
{
builder.Add(application);
}
}
return builder.ToImmutable();
}
/// <summary> /// <summary>
/// Executes the specified query and returns the first element. /// Executes the specified query and returns the first element.
/// </summary> /// </summary>
@ -301,7 +342,8 @@ namespace OpenIddict.Stores
// Note: parsing the stringified resources is an expensive operation. // Note: parsing the stringified resources is an expensive operation.
// To mitigate that, the resulting array is stored in the memory cache. // To mitigate that, the resulting array is stored in the memory cache.
var resources = Cache.GetOrCreate("b6148250-aede-4fb9-a621-07c9bcf238c3", entry => var key = string.Concat("b6148250-aede-4fb9-a621-07c9bcf238c3", "\x1e", scope.Resources);
var resources = Cache.GetOrCreate(key, entry =>
{ {
entry.SetPriority(CacheItemPriority.High) entry.SetPriority(CacheItemPriority.High)
.SetSlidingExpiration(TimeSpan.FromMinutes(1)); .SetSlidingExpiration(TimeSpan.FromMinutes(1));

Loading…
Cancel
Save