Add CT parameter to IResourceStore and IResourceStoreExtensions, flow through all implementations and tests

This commit is contained in:
Damian Hickey 2026-02-20 19:04:23 +01:00
parent a533c397c0
commit 5df903ae44
17 changed files with 134 additions and 105 deletions

View file

@ -43,7 +43,7 @@ public class Index : PageModel
var client = await _clients.FindClientByIdAsync(grant.ClientId, HttpContext.RequestAborted);
if (client != null)
{
var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes);
var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes, HttpContext.RequestAborted);
var item = new GrantViewModel()
{

View file

@ -52,8 +52,9 @@ public class ResourceStore : IResourceStore
/// Finds the API resources by name.
/// </summary>
/// <param name="apiResourceNames">The names.</param>
/// <param name="ct"></param>
/// <returns></returns>
public virtual async Task<IEnumerable<ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> apiResourceNames)
public virtual async Task<IEnumerable<ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> apiResourceNames, CT ct)
{
using var activity = Tracing.StoreActivitySource.StartActivity("ResourceStore.FindApiResourcesByName");
activity?.SetTag(Tracing.Properties.ApiResourceNames, apiResourceNames.ToSpaceSeparatedString());
@ -72,7 +73,7 @@ public class ResourceStore : IResourceStore
.Include(x => x.Properties)
.AsNoTracking();
var result = (await apis.ToArrayAsync(CancellationTokenProvider.CancellationToken))
var result = (await apis.ToArrayAsync(ct))
.Where(x => apiResourceNames.Contains(x.Name))
.Select(x => x.ToModel()).ToArray();
@ -92,8 +93,9 @@ public class ResourceStore : IResourceStore
/// Gets API resources by scope name.
/// </summary>
/// <param name="scopeNames"></param>
/// <param name="ct"></param>
/// <returns></returns>
public virtual async Task<IEnumerable<ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> scopeNames)
public virtual async Task<IEnumerable<ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> scopeNames, CT ct)
{
using var activity = Tracing.StoreActivitySource.StartActivity("ResourceStore.FindApiResourcesByScopeName");
activity?.SetTag(Tracing.Properties.ScopeNames, scopeNames.ToSpaceSeparatedString());
@ -112,7 +114,7 @@ public class ResourceStore : IResourceStore
.Include(x => x.Properties)
.AsNoTracking();
var results = (await apis.ToArrayAsync(CancellationTokenProvider.CancellationToken))
var results = (await apis.ToArrayAsync(ct))
.Where(api => api.Scopes.Any(x => names.Contains(x.Scope)));
var models = results.Select(x => x.ToModel()).ToArray();
@ -125,8 +127,9 @@ public class ResourceStore : IResourceStore
/// Gets identity resources by scope name.
/// </summary>
/// <param name="scopeNames"></param>
/// <param name="ct"></param>
/// <returns></returns>
public virtual async Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> scopeNames)
public virtual async Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> scopeNames, CT ct)
{
using var activity = Tracing.StoreActivitySource.StartActivity("ResourceStore.FindIdentityResourcesByScopeName");
activity?.SetTag(Tracing.Properties.ScopeNames, scopeNames.ToSpaceSeparatedString());
@ -143,7 +146,7 @@ public class ResourceStore : IResourceStore
.Include(x => x.Properties)
.AsNoTracking();
var results = (await resources.ToArrayAsync(CancellationTokenProvider.CancellationToken))
var results = (await resources.ToArrayAsync(ct))
.Where(x => scopes.Contains(x.Name));
Logger.LogDebug("Found {scopes} identity scopes in database", results.Select(x => x.Name));
@ -155,8 +158,9 @@ public class ResourceStore : IResourceStore
/// Gets scopes by scope name.
/// </summary>
/// <param name="scopeNames"></param>
/// <param name="ct"></param>
/// <returns></returns>
public virtual async Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopeNames)
public virtual async Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopeNames, CT ct)
{
using var activity = Tracing.StoreActivitySource.StartActivity("ResourceStore.FindApiScopesByName");
activity?.SetTag(Tracing.Properties.ScopeNames, scopeNames.ToSpaceSeparatedString());
@ -173,7 +177,7 @@ public class ResourceStore : IResourceStore
.Include(x => x.Properties)
.AsNoTracking();
var results = (await resources.ToArrayAsync(CancellationTokenProvider.CancellationToken))
var results = (await resources.ToArrayAsync(ct))
.Where(x => scopes.Contains(x.Name));
Logger.LogDebug("Found {scopes} scopes in database", results.Select(x => x.Name));
@ -185,7 +189,7 @@ public class ResourceStore : IResourceStore
/// Gets all resources.
/// </summary>
/// <returns></returns>
public virtual async Task<Resources> GetAllResourcesAsync()
public virtual async Task<Resources> GetAllResourcesAsync(CT ct)
{
using var activity = Tracing.StoreActivitySource.StartActivity("ResourceStore.GetAllResources");
@ -207,9 +211,9 @@ public class ResourceStore : IResourceStore
.AsNoTracking();
var result = new Resources(
(await identity.ToArrayAsync(CancellationTokenProvider.CancellationToken)).Select(x => x.ToModel()),
(await apis.ToArrayAsync(CancellationTokenProvider.CancellationToken)).Select(x => x.ToModel()),
(await scopes.ToArrayAsync(CancellationTokenProvider.CancellationToken)).Select(x => x.ToModel())
(await identity.ToArrayAsync(ct)).Select(x => x.ToModel()),
(await apis.ToArrayAsync(ct)).Select(x => x.ToModel()),
(await scopes.ToArrayAsync(ct)).Select(x => x.ToModel())
);
Logger.LogDebug("Found {scopes} as all scopes, and {apis} as API resources",

View file

@ -16,12 +16,13 @@ public static class IResourceStoreExtensions
/// </summary>
/// <param name="store">The store.</param>
/// <param name="scopeNames">The scope names.</param>
/// <param name="ct">The <see cref="CT"/> used to propagate notifications that the operation should be cancelled.</param>
/// <returns></returns>
public static async Task<Resources> FindResourcesByScopeAsync(this IResourceStore store, IEnumerable<string> scopeNames)
public static async Task<Resources> FindResourcesByScopeAsync(this IResourceStore store, IEnumerable<string> scopeNames, CT ct)
{
var identity = await store.FindIdentityResourcesByScopeNameAsync(scopeNames);
var apiResources = await store.FindApiResourcesByScopeNameAsync(scopeNames);
var scopes = await store.FindApiScopesByNameAsync(scopeNames);
var identity = await store.FindIdentityResourcesByScopeNameAsync(scopeNames, ct);
var apiResources = await store.FindApiResourcesByScopeNameAsync(scopeNames, ct);
var scopes = await store.FindApiScopesByNameAsync(scopeNames, ct);
ValidateNameUniqueness(identity, apiResources, scopes);
@ -88,17 +89,19 @@ public static class IResourceStoreExtensions
/// </summary>
/// <param name="store">The store.</param>
/// <param name="scopeNames">The scope names.</param>
/// <param name="ct">The <see cref="CT"/> used to propagate notifications that the operation should be cancelled.</param>
/// <returns></returns>
public static async Task<Resources> FindEnabledResourcesByScopeAsync(this IResourceStore store, IEnumerable<string> scopeNames) => (await store.FindResourcesByScopeAsync(scopeNames)).FilterEnabled();
public static async Task<Resources> FindEnabledResourcesByScopeAsync(this IResourceStore store, IEnumerable<string> scopeNames, CT ct) => (await store.FindResourcesByScopeAsync(scopeNames, ct)).FilterEnabled();
/// <summary>
/// Gets all enabled resources.
/// </summary>
/// <param name="store">The store.</param>
/// <param name="ct">The <see cref="CT"/> used to propagate notifications that the operation should be cancelled.</param>
/// <returns></returns>
public static async Task<Resources> GetAllEnabledResourcesAsync(this IResourceStore store)
public static async Task<Resources> GetAllEnabledResourcesAsync(this IResourceStore store, CT ct)
{
var resources = await store.GetAllResourcesAsync();
var resources = await store.GetAllResourcesAsync(ct);
ValidateNameUniqueness(resources.IdentityResources, resources.ApiResources, resources.ApiScopes);
return resources.FilterEnabled();
@ -109,11 +112,15 @@ public static class IResourceStoreExtensions
/// </summary>
/// <param name="store">The store.</param>
/// <param name="scopeNames">The scope names.</param>
/// <param name="ct">The <see cref="CT"/> used to propagate notifications that the operation should be cancelled.</param>
/// <returns></returns>
public static async Task<IEnumerable<IdentityResource>> FindEnabledIdentityResourcesByScopeAsync(this IResourceStore store, IEnumerable<string> scopeNames) => (await store.FindIdentityResourcesByScopeNameAsync(scopeNames)).Where(x => x.Enabled).ToArray();
public static async Task<IEnumerable<IdentityResource>> FindEnabledIdentityResourcesByScopeAsync(this IResourceStore store, IEnumerable<string> scopeNames, CT ct) => (await store.FindIdentityResourcesByScopeNameAsync(scopeNames, ct)).Where(x => x.Enabled).ToArray();
/// <summary>
/// Finds the enabled API resources by name.
/// </summary>
public static async Task<IEnumerable<ApiResource>> FindEnabledApiResourcesByNameAsync(this IResourceStore store, IEnumerable<string> resourceNames) => (await store.FindApiResourcesByNameAsync(resourceNames)).Where(x => x.Enabled).ToArray();
/// <param name="store">The store.</param>
/// <param name="resourceNames">The resource names.</param>
/// <param name="ct">The <see cref="CT"/> used to propagate notifications that the operation should be cancelled.</param>
public static async Task<IEnumerable<ApiResource>> FindEnabledApiResourcesByNameAsync(this IResourceStore store, IEnumerable<string> resourceNames, CT ct) => (await store.FindApiResourcesByNameAsync(resourceNames, ct)).Where(x => x.Enabled).ToArray();
}

View file

@ -236,7 +236,7 @@ public class DiscoveryResponseGenerator : IDiscoveryResponseGenerator
Options.Discovery.ShowApiScopes ||
Options.Discovery.ShowClaims)
{
var resources = await ResourceStore.GetAllEnabledResourcesAsync();
var resources = await ResourceStore.GetAllEnabledResourcesAsync(default);
var scopes = new List<string>();
// scopes

View file

@ -121,7 +121,7 @@ public class UserInfoResponseGenerator : IUserInfoResponseGenerator
Logger.LogDebug("Scopes in access token: {scopes}", scopeString);
// if we ever parameterized identity scopes, then we would need to invoke the resource validator's parse API here
var identityResources = await Resources.FindEnabledIdentityResourcesByScopeAsync(scopes);
var identityResources = await Resources.FindEnabledIdentityResourcesByScopeAsync(scopes, default);
var resources = new Resources(identityResources, Enumerable.Empty<ApiResource>(), Enumerable.Empty<ApiScope>());
var result = new ResourceValidationResult(resources);

View file

@ -80,7 +80,7 @@ public class CachingResourceStore<T> : IResourceStore
}
/// <inheritdoc/>
public async Task<Resources> GetAllResourcesAsync()
public async Task<Resources> GetAllResourcesAsync(CT ct)
{
using var activity = Tracing.StoreActivitySource.StartActivity("CachingResourceStore.GetAllResources");
@ -88,13 +88,13 @@ public class CachingResourceStore<T> : IResourceStore
var all = await _allCache.GetOrAddAsync(key,
_options.Caching.ResourceStoreExpiration,
async () => await _inner.GetAllResourcesAsync());
async () => await _inner.GetAllResourcesAsync(ct));
return all;
}
/// <inheritdoc/>
public async Task<IEnumerable<ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> scopeNames)
public async Task<IEnumerable<ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> scopeNames, CT ct)
{
using var activity = Tracing.StoreActivitySource.StartActivity("CachingResourceStore.FindApiResourcesByScopeName");
activity?.SetTag(Tracing.Properties.ScopeNames, scopeNames.ToSpaceSeparatedString());
@ -133,7 +133,7 @@ public class CachingResourceStore<T> : IResourceStore
// do the cache/DB lookup
var resources = await _allCache.GetOrAddAsync(allCacheItemsKey, itemsDuration, async () =>
{
var results = await _inner.FindApiResourcesByScopeNameAsync(uncachedScopes);
var results = await _inner.FindApiResourcesByScopeNameAsync(uncachedScopes, ct);
return new Resources(null, results, null);
});
@ -160,51 +160,52 @@ public class CachingResourceStore<T> : IResourceStore
}
// now that we have all the ApiResource names, just use our other API (that should find the cacted items)
return await FindApiResourcesByNameAsync(apiResourceNames);
return await FindApiResourcesByNameAsync(apiResourceNames, ct);
}
/// <inheritdoc/>
public async Task<IEnumerable<ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> apiResourceNames)
public async Task<IEnumerable<ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> apiResourceNames, CT ct)
{
using var activity = Tracing.StoreActivitySource.StartActivity("CachingResourceStore.FindApiResourcesByName");
activity?.SetTag(Tracing.Properties.ApiResourceNames, apiResourceNames.ToSpaceSeparatedString());
return await FindItemsAsync(apiResourceNames, _apiResourceCache,
async names => new Resources(null, await _inner.FindApiResourcesByNameAsync(names), null),
x => x.ApiResources, x => x.Name, "ApiResources-");
async (names, innerCt) => new Resources(null, await _inner.FindApiResourcesByNameAsync(names, innerCt), null),
x => x.ApiResources, x => x.Name, "ApiResources-", ct);
}
/// <inheritdoc/>
public async Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> scopeNames)
public async Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> scopeNames, CT ct)
{
using var activity = Tracing.StoreActivitySource.StartActivity("CachingResourceStore.FindIdentityResourcesByScopeName");
activity?.SetTag(Tracing.Properties.ScopeNames, scopeNames.ToSpaceSeparatedString());
return await FindItemsAsync(scopeNames, _identityCache,
async names => new Resources(await _inner.FindIdentityResourcesByScopeNameAsync(names), null, null),
x => x.IdentityResources, x => x.Name, "IdentityResources-");
async (names, innerCt) => new Resources(await _inner.FindIdentityResourcesByScopeNameAsync(names, innerCt), null, null),
x => x.IdentityResources, x => x.Name, "IdentityResources-", ct);
}
/// <inheritdoc/>
public async Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopeNames)
public async Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopeNames, CT ct)
{
using var activity = Tracing.StoreActivitySource.StartActivity("CachingResourceStore.FindApiScopesByName");
activity?.SetTag(Tracing.Properties.ScopeNames, scopeNames.ToSpaceSeparatedString());
return await FindItemsAsync(scopeNames, _apiScopeCache,
async names => new Resources(null, null, await _inner.FindApiScopesByNameAsync(names)),
x => x.ApiScopes, x => x.Name, "ApiScopes-");
async (names, innerCt) => new Resources(null, null, await _inner.FindApiScopesByNameAsync(names, innerCt)),
x => x.ApiScopes, x => x.Name, "ApiScopes-", ct);
}
private async Task<IEnumerable<TItem>> FindItemsAsync<TItem>(
IEnumerable<string> names,
ICache<TItem> cache,
Func<IEnumerable<string>, Task<Resources>> getResourcesFunc,
Func<IEnumerable<string>, CT, Task<Resources>> getResourcesFunc,
Func<Resources, IEnumerable<TItem>> getFromResourcesFunc,
Func<TItem, string> getNameFunc,
string allCachePrefix
string allCachePrefix,
CT ct
)
where TItem : class
{
@ -237,7 +238,7 @@ public class CachingResourceStore<T> : IResourceStore
// expire this entry much faster than the normal items
var itemsDuration = _options.Caching.ResourceStoreExpiration / 20;
// do the cache/DB lookup
var resources = await _allCache.GetOrAddAsync(allCacheItemsKey, itemsDuration, async () => await getResourcesFunc(uncachedNames));
var resources = await _allCache.GetOrAddAsync(allCacheItemsKey, itemsDuration, async () => await getResourcesFunc(uncachedNames, ct));
// get the specific items from the Resources object
var uncachedItems = getFromResourcesFunc(resources);

View file

@ -8,13 +8,13 @@ namespace Duende.IdentityServer.Stores.Empty;
internal class EmptyResourceStore : IResourceStore
{
public Task<IEnumerable<ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> apiResourceNames) => Task.FromResult(Enumerable.Empty<ApiResource>());
public Task<IEnumerable<ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> apiResourceNames, CT ct) => Task.FromResult(Enumerable.Empty<ApiResource>());
public Task<IEnumerable<ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> scopeNames) => Task.FromResult(Enumerable.Empty<ApiResource>());
public Task<IEnumerable<ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> scopeNames, CT ct) => Task.FromResult(Enumerable.Empty<ApiResource>());
public Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopeNames) => Task.FromResult(Enumerable.Empty<ApiScope>());
public Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopeNames, CT ct) => Task.FromResult(Enumerable.Empty<ApiScope>());
public Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> scopeNames) => Task.FromResult(Enumerable.Empty<IdentityResource>());
public Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> scopeNames, CT ct) => Task.FromResult(Enumerable.Empty<IdentityResource>());
public Task<Resources> GetAllResourcesAsync() => Task.FromResult(new Resources() { OfflineAccess = true });
public Task<Resources> GetAllResourcesAsync(CT ct) => Task.FromResult(new Resources() { OfflineAccess = true });
}

View file

@ -45,7 +45,7 @@ public class InMemoryResourcesStore : IResourceStore
}
/// <inheritdoc/>
public Task<Resources> GetAllResourcesAsync()
public Task<Resources> GetAllResourcesAsync(CT ct)
{
using var activity = Tracing.StoreActivitySource.StartActivity("InMemoryResourceStore.GetAllResources");
@ -54,7 +54,7 @@ public class InMemoryResourcesStore : IResourceStore
}
/// <inheritdoc/>
public Task<IEnumerable<ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> apiResourceNames)
public Task<IEnumerable<ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> apiResourceNames, CT ct)
{
ArgumentNullException.ThrowIfNull(apiResourceNames);
using var activity = Tracing.StoreActivitySource.StartActivity("InMemoryResourceStore.FindApiResourcesByName");
@ -67,7 +67,7 @@ public class InMemoryResourcesStore : IResourceStore
}
/// <inheritdoc/>
public Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> scopeNames)
public Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> scopeNames, CT ct)
{
ArgumentNullException.ThrowIfNull(scopeNames);
using var activity = Tracing.StoreActivitySource.StartActivity("InMemoryResourceStore.FindIdentityResourcesByScopeName");
@ -81,7 +81,7 @@ public class InMemoryResourcesStore : IResourceStore
}
/// <inheritdoc/>
public Task<IEnumerable<ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> scopeNames)
public Task<IEnumerable<ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> scopeNames, CT ct)
{
ArgumentNullException.ThrowIfNull(scopeNames);
using var activity = Tracing.StoreActivitySource.StartActivity("InMemoryResourceStore.FindApiResourcesByScopeName");
@ -95,7 +95,7 @@ public class InMemoryResourcesStore : IResourceStore
}
/// <inheritdoc/>
public Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopeNames)
public Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopeNames, CT ct)
{
ArgumentNullException.ThrowIfNull(scopeNames);
using var activity = Tracing.StoreActivitySource.StartActivity("InMemoryResourceStore.FindApiScopesByName");

View file

@ -64,7 +64,7 @@ public class ApiSecretValidator : IApiSecretValidator
}
// load API resource
var apis = await _resources.FindApiResourcesByNameAsync(new[] { parsedSecret.Id });
var apis = await _resources.FindApiResourcesByNameAsync(new[] { parsedSecret.Id }, context.RequestAborted);
if (apis == null || !apis.Any())
{
await RaiseFailureEventAsync(parsedSecret.Id, "Unknown API resource");

View file

@ -55,7 +55,7 @@ public class DefaultResourceValidator : IResourceValidator
var scopeNames = parsedScopesResult.ParsedScopes.Select(x => x.ParsedName).Distinct().ToArray();
// todo: this API might want to pass resource indicators to better filter
var scopeResourcesFromStore = await _store.FindEnabledResourcesByScopeAsync(scopeNames);
var scopeResourcesFromStore = await _store.FindEnabledResourcesByScopeAsync(scopeNames, default);
if (request.ResourceIndicators?.Any() == true)
{

View file

@ -1106,12 +1106,12 @@ internal class TokenRequestValidator : ITokenRequestValidator
var clientAllowedScopes = new List<string>();
if (!ignoreImplicitIdentityScopes)
{
var resources = await _resourceStore.FindResourcesByScopeAsync(_validatedRequest.Client.AllowedScopes);
var resources = await _resourceStore.FindResourcesByScopeAsync(_validatedRequest.Client.AllowedScopes, _ct);
clientAllowedScopes.AddRange(resources.ToScopeNames().Where(x => _validatedRequest.Client.AllowedScopes.Contains(x)));
}
else
{
var apiScopes = await _resourceStore.FindApiScopesByNameAsync(_validatedRequest.Client.AllowedScopes);
var apiScopes = await _resourceStore.FindApiScopesByNameAsync(_validatedRequest.Client.AllowedScopes, _ct);
clientAllowedScopes.AddRange(apiScopes.Select(x => x.Name));
}

View file

@ -16,25 +16,34 @@ public interface IResourceStore
/// <summary>
/// Gets identity resources by scope name.
/// </summary>
Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> scopeNames);
/// <param name="scopeNames">The scope names.</param>
/// <param name="ct">The <see cref="CT"/> used to propagate notifications that the operation should be cancelled.</param>
Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> scopeNames, CT ct);
/// <summary>
/// Gets API scopes by scope name.
/// </summary>
Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopeNames);
/// <param name="scopeNames">The scope names.</param>
/// <param name="ct">The <see cref="CT"/> used to propagate notifications that the operation should be cancelled.</param>
Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopeNames, CT ct);
/// <summary>
/// Gets API resources by scope name.
/// </summary>
Task<IEnumerable<ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> scopeNames);
/// <param name="scopeNames">The scope names.</param>
/// <param name="ct">The <see cref="CT"/> used to propagate notifications that the operation should be cancelled.</param>
Task<IEnumerable<ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> scopeNames, CT ct);
/// <summary>
/// Gets API resources by API resource name.
/// </summary>
Task<IEnumerable<ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> apiResourceNames);
/// <param name="apiResourceNames">The API resource names.</param>
/// <param name="ct">The <see cref="CT"/> used to propagate notifications that the operation should be cancelled.</param>
Task<IEnumerable<ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> apiResourceNames, CT ct);
/// <summary>
/// Gets all resources.
/// </summary>
Task<Resources> GetAllResourcesAsync();
/// <param name="ct">The <see cref="CT"/> used to propagate notifications that the operation should be cancelled.</param>
Task<Resources> GetAllResourcesAsync(CT ct);
}

View file

@ -16,6 +16,8 @@ namespace Duende.IdentityServer.IntegrationTests.EntityFramework.Storage.Stores;
public class ScopeStoreTests : IntegrationTest<ScopeStoreTests, ConfigurationDbContext, ConfigurationStoreOptions>
{
private readonly CT _ct = TestContext.Current.CancellationToken;
public ScopeStoreTests(DatabaseProviderFixture<ConfigurationDbContext> fixture) : base(fixture)
{
foreach (var options in TestDatabaseProviders)
@ -76,7 +78,7 @@ public class ScopeStoreTests : IntegrationTest<ScopeStoreTests, ConfigurationDbC
await using (var context = new ConfigurationDbContext(options))
{
var store = new ResourceStore(context, new NullLogger<ResourceStore>(), new NoneCancellationTokenProvider());
foundResource = (await store.FindApiResourcesByNameAsync(new[] { resource.Name })).SingleOrDefault();
foundResource = (await store.FindApiResourcesByNameAsync(new[] { resource.Name }, _ct)).SingleOrDefault();
}
foundResource.ShouldNotBeNull();
@ -105,7 +107,7 @@ public class ScopeStoreTests : IntegrationTest<ScopeStoreTests, ConfigurationDbC
await using (var context = new ConfigurationDbContext(options))
{
var store = new ResourceStore(context, new NullLogger<ResourceStore>(), new NoneCancellationTokenProvider());
foundResource = (await store.FindApiResourcesByNameAsync(new[] { resource.Name })).SingleOrDefault();
foundResource = (await store.FindApiResourcesByNameAsync(new[] { resource.Name }, _ct)).SingleOrDefault();
}
foundResource.ShouldNotBeNull();
@ -140,7 +142,7 @@ public class ScopeStoreTests : IntegrationTest<ScopeStoreTests, ConfigurationDbC
resources = await store.FindApiResourcesByScopeNameAsync(new List<string>
{
testApiScope.Name
});
}, _ct);
}
resources.ShouldNotBeNull();
@ -171,7 +173,7 @@ public class ScopeStoreTests : IntegrationTest<ScopeStoreTests, ConfigurationDbC
await using (var context = new ConfigurationDbContext(options))
{
var store = new ResourceStore(context, new NullLogger<ResourceStore>(), new NoneCancellationTokenProvider());
resources = await store.FindApiResourcesByScopeNameAsync(new[] { testApiScope.Name });
resources = await store.FindApiResourcesByScopeNameAsync(new[] { testApiScope.Name }, _ct);
}
resources.ShouldNotBeNull();
@ -197,7 +199,7 @@ public class ScopeStoreTests : IntegrationTest<ScopeStoreTests, ConfigurationDbC
resources = (await store.FindIdentityResourcesByScopeNameAsync(new List<string>
{
resource.Name
})).ToList();
}, _ct)).ToList();
}
resources.ShouldNotBeNull();
@ -228,7 +230,7 @@ public class ScopeStoreTests : IntegrationTest<ScopeStoreTests, ConfigurationDbC
resources = (await store.FindIdentityResourcesByScopeNameAsync(new List<string>
{
resource.Name
})).ToList();
}, _ct)).ToList();
}
resources.ShouldNotBeNull();
@ -254,7 +256,7 @@ public class ScopeStoreTests : IntegrationTest<ScopeStoreTests, ConfigurationDbC
resources = (await store.FindApiScopesByNameAsync(new List<string>
{
resource.Name
})).ToList();
}, _ct)).ToList();
}
resources.ShouldNotBeNull();
@ -285,7 +287,7 @@ public class ScopeStoreTests : IntegrationTest<ScopeStoreTests, ConfigurationDbC
resources = (await store.FindApiScopesByNameAsync(new List<string>
{
resource.Name
})).ToList();
}, _ct)).ToList();
}
resources.ShouldNotBeNull();
@ -329,7 +331,7 @@ public class ScopeStoreTests : IntegrationTest<ScopeStoreTests, ConfigurationDbC
await using (var context = new ConfigurationDbContext(options))
{
var store = new ResourceStore(context, new NullLogger<ResourceStore>(), new NoneCancellationTokenProvider());
resources = await store.GetAllResourcesAsync();
resources = await store.GetAllResourcesAsync(_ct);
}
resources.ShouldNotBeNull();

View file

@ -13,6 +13,8 @@ namespace IdentityServer.UnitTests.Caches;
public class ResourceStoreCacheTests
{
private readonly CT _ct = TestContext.Current.CancellationToken;
private List<Client> _clients { get; set; } = new List<Client>();
private List<IdentityResource> _identityResources { get; set; } = new List<IdentityResource>();
private List<ApiResource> _resources { get; set; } = new List<ApiResource>();
@ -55,7 +57,7 @@ public class ResourceStoreCacheTests
var store = _provider.GetRequiredService<IResourceStore>();
cache.CacheItems.Count.ShouldBe(0);
var results = await store.FindIdentityResourcesByScopeNameAsync(new[] { "profile" });
var results = await store.FindIdentityResourcesByScopeNameAsync(new[] { "profile" }, _ct);
cache.CacheItems.Count.ShouldBe(1);
cache.CacheItems.First().Value.Value.Name.ShouldBe("profile");
@ -69,7 +71,7 @@ public class ResourceStoreCacheTests
var store = _provider.GetRequiredService<IResourceStore>();
cache.CacheItems.Count.ShouldBe(0);
var results = await store.FindApiResourcesByScopeNameAsync(new[] { "scope1" });
var results = await store.FindApiResourcesByScopeNameAsync(new[] { "scope1" }, _ct);
cache.CacheItems.Count.ShouldBe(1);
cache.CacheItems.First().Value.Value.Names.Single().ShouldBe("urn:api1");
@ -82,7 +84,7 @@ public class ResourceStoreCacheTests
var store = _provider.GetRequiredService<IResourceStore>();
cache.CacheItems.Count.ShouldBe(0);
var results = await store.FindApiScopesByNameAsync(new[] { "scope1" });
var results = await store.FindApiScopesByNameAsync(new[] { "scope1" }, _ct);
cache.CacheItems.Count.ShouldBe(1);
cache.CacheItems.First().Value.Value.Name.ShouldBe("scope1");

View file

@ -9,6 +9,8 @@ namespace UnitTests.Extensions;
public class IResourceStoreExtensionsTests
{
private readonly CT _ct = TestContext.Current.CancellationToken;
[Fact]
public async Task GetAllEnabledResourcesAsync_on_duplicate_identity_scopes_should_fail()
{
@ -19,7 +21,7 @@ public class IResourceStoreExtensionsTests
new IdentityResource { Name = "A" } }
};
Func<Task> a = () => store.GetAllEnabledResourcesAsync();
Func<Task> a = () => store.GetAllEnabledResourcesAsync(_ct);
var exception = await a.ShouldThrowAsync<Exception>();
exception.Message.ShouldMatch("Duplicate identity scopes*");
}
@ -34,7 +36,7 @@ public class IResourceStoreExtensionsTests
new IdentityResource { Name = "B" } }
};
await store.GetAllEnabledResourcesAsync();
await store.GetAllEnabledResourcesAsync(_ct);
}
[Fact]
@ -45,7 +47,7 @@ public class IResourceStoreExtensionsTests
ApiResources = { new ApiResource { Name = "a" }, new ApiResource { Name = "a" } }
};
Func<Task> a = () => store.GetAllEnabledResourcesAsync();
Func<Task> a = () => store.GetAllEnabledResourcesAsync(_ct);
var exception = await a.ShouldThrowAsync<Exception>();
exception.Message.ShouldMatch("Duplicate api resources*");
}
@ -58,7 +60,7 @@ public class IResourceStoreExtensionsTests
ApiResources = { new ApiResource("A"), new ApiResource("B") }
};
await store.GetAllEnabledResourcesAsync();
await store.GetAllEnabledResourcesAsync(_ct);
}
[Fact]
@ -71,7 +73,7 @@ public class IResourceStoreExtensionsTests
new IdentityResource { Name = "A" } }
};
Func<Task> a = () => store.FindResourcesByScopeAsync(new string[] { "A" });
Func<Task> a = () => store.FindResourcesByScopeAsync(new string[] { "A" }, _ct);
var exception = await a.ShouldThrowAsync<Exception>();
exception.Message.ShouldMatch("Duplicate identity scopes*");
}
@ -86,7 +88,7 @@ public class IResourceStoreExtensionsTests
new IdentityResource { Name = "B" } }
};
await store.FindResourcesByScopeAsync(new string[] { "A" });
await store.FindResourcesByScopeAsync(new string[] { "A" }, _ct);
}
[Fact]
@ -103,7 +105,7 @@ public class IResourceStoreExtensionsTests
}
};
var result = await store.FindResourcesByScopeAsync(new string[] { "a" });
var result = await store.FindResourcesByScopeAsync(new string[] { "a" }, _ct);
result.ApiResources.Count.ShouldBe(2);
result.ApiScopes.Count.ShouldBe(1);
result.ApiResources.Select(x => x.Name).ShouldBe(["api1", "api2"]);
@ -118,7 +120,7 @@ public class IResourceStoreExtensionsTests
ApiResources = { new ApiResource("A"), new ApiResource("B") }
};
await store.FindResourcesByScopeAsync(new string[] { "A" });
await store.FindResourcesByScopeAsync(new string[] { "A" }, _ct);
}
[Fact]
@ -136,7 +138,7 @@ public class IResourceStoreExtensionsTests
}
};
var result = await store.FindResourcesByScopeAsync(new string[] { "a" });
var result = await store.FindResourcesByScopeAsync(new string[] { "a" }, _ct);
result.ApiResources.Count.ShouldBe(1);
}
@ -146,7 +148,7 @@ public class IResourceStoreExtensionsTests
public List<ApiResource> ApiResources { get; set; } = new List<ApiResource>();
public List<ApiScope> ApiScopes { get; set; } = new List<ApiScope>();
public Task<IEnumerable<ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> names)
public Task<IEnumerable<ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> names, CT ct)
{
var apis = from a in ApiResources
where names.Contains(a.Name)
@ -154,7 +156,7 @@ public class IResourceStoreExtensionsTests
return Task.FromResult(apis);
}
public Task<IEnumerable<ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> names)
public Task<IEnumerable<ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> names, CT ct)
{
ArgumentNullException.ThrowIfNull(names);
@ -165,7 +167,7 @@ public class IResourceStoreExtensionsTests
return Task.FromResult(api);
}
public Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> names)
public Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> names, CT ct)
{
ArgumentNullException.ThrowIfNull(names);
@ -176,7 +178,7 @@ public class IResourceStoreExtensionsTests
return Task.FromResult(identity);
}
public Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopeNames)
public Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopeNames, CT ct)
{
var q = from x in ApiScopes
where scopeNames.Contains(x.Name)
@ -184,7 +186,7 @@ public class IResourceStoreExtensionsTests
return Task.FromResult(q);
}
public Task<Resources> GetAllResourcesAsync()
public Task<Resources> GetAllResourcesAsync(CT ct)
{
var result = new Resources(IdentityResources, ApiResources, ApiScopes);
return Task.FromResult(result);

View file

@ -22,15 +22,15 @@ public class IdentityServerBuilderExtensionsCacheStoreTests
private class CustomResourceStore : IResourceStore
{
public Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> scopeNames) => throw new System.NotImplementedException();
public Task<IEnumerable<IdentityResource>> FindIdentityResourcesByScopeNameAsync(IEnumerable<string> scopeNames, CT ct) => throw new System.NotImplementedException();
public Task<IEnumerable<ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> scopeNames) => throw new System.NotImplementedException();
public Task<IEnumerable<ApiResource>> FindApiResourcesByScopeNameAsync(IEnumerable<string> scopeNames, CT ct) => throw new System.NotImplementedException();
public Task<IEnumerable<ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> names) => throw new System.NotImplementedException();
public Task<IEnumerable<ApiResource>> FindApiResourcesByNameAsync(IEnumerable<string> names, CT ct) => throw new System.NotImplementedException();
public Task<Resources> GetAllResourcesAsync() => throw new System.NotImplementedException();
public Task<Resources> GetAllResourcesAsync(CT ct) => throw new System.NotImplementedException();
public Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopeNames) => throw new System.NotImplementedException();
public Task<IEnumerable<ApiScope>> FindApiScopesByNameAsync(IEnumerable<string> scopeNames, CT ct) => throw new System.NotImplementedException();
}
[Fact]

View file

@ -11,6 +11,8 @@ namespace UnitTests.Stores.Default;
public class CachingResourceStoreTests
{
private readonly CT _ct = TestContext.Current.CancellationToken;
private List<IdentityResource> _identityResources = new List<IdentityResource>();
private List<ApiResource> _apiResources = new List<ApiResource>();
private List<ApiScope> _apiScopes = new List<ApiScope>();
@ -48,7 +50,7 @@ public class CachingResourceStoreTests
_scopeCache.Items.Count.ShouldBe(0);
var items = await _subject.FindApiScopesByNameAsync(new[] { "scope3", "scope1", "scope2", "invalid" });
var items = await _subject.FindApiScopesByNameAsync(new[] { "scope3", "scope1", "scope2", "invalid" }, _ct);
items.Count().ShouldBe(3);
_scopeCache.Items.Count.ShouldBe(3);
@ -64,23 +66,23 @@ public class CachingResourceStoreTests
_scopeCache.Items.Count.ShouldBe(0);
var items = await _subject.FindApiScopesByNameAsync(new[] { "scope1" });
var items = await _subject.FindApiScopesByNameAsync(new[] { "scope1" }, _ct);
items.Count().ShouldBe(1);
_scopeCache.Items.Count.ShouldBe(1);
_apiScopes.Remove(_apiScopes.Single(x => x.Name == "scope1"));
items = await _subject.FindApiScopesByNameAsync(new[] { "scope1", "scope2" });
items = await _subject.FindApiScopesByNameAsync(new[] { "scope1", "scope2" }, _ct);
items.Count().ShouldBe(2);
_scopeCache.Items.Count.ShouldBe(2);
_apiScopes.Remove(_apiScopes.Single(x => x.Name == "scope2"));
items = await _subject.FindApiScopesByNameAsync(new[] { "scope3", "scope2", "scope4" });
items = await _subject.FindApiScopesByNameAsync(new[] { "scope3", "scope2", "scope4" }, _ct);
items.Count().ShouldBe(3);
_scopeCache.Items.Count.ShouldBe(4);
// this shows we will find it in the cache, even if removed from the DB
_apiScopes.Remove(_apiScopes.Single(x => x.Name == "scope3"));
items = await _subject.FindApiScopesByNameAsync(new[] { "scope3", "scope1", "scope2" });
items = await _subject.FindApiScopesByNameAsync(new[] { "scope3", "scope1", "scope2" }, _ct);
items.Count().ShouldBe(3);
_scopeCache.Items.Count.ShouldBe(4);
}
@ -98,7 +100,7 @@ public class CachingResourceStoreTests
{
_apiCache.Items.Count.ShouldBe(0);
_apiResourceNamesCache.Items.Count.ShouldBe(0);
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "invalid" });
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "invalid" }, _ct);
items.Count().ShouldBe(0);
_apiCache.Items.Count.ShouldBe(0);
_apiResourceNamesCache.Items.Count.ShouldBe(1);
@ -111,7 +113,7 @@ public class CachingResourceStoreTests
_apiCache.Items.Count.ShouldBe(0);
_apiResourceNamesCache.Items.Count.ShouldBe(0);
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo1" });
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo1" }, _ct);
items.Count().ShouldBe(1);
items.Select(x => x.Name).ShouldBe(new[] { "foo" });
_apiCache.Items.Count.ShouldBe(1);
@ -119,7 +121,7 @@ public class CachingResourceStoreTests
}
{
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo2" });
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo2" }, _ct);
items.Count().ShouldBe(1);
items.Select(x => x.Name).ShouldBe(["foo"]);
_apiCache.Items.Count.ShouldBe(1);
@ -127,7 +129,7 @@ public class CachingResourceStoreTests
}
{
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo1", "bar1" });
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo1", "bar1" }, _ct);
items.Count().ShouldBe(2);
items.Select(x => x.Name).ShouldBe(["foo", "bar"]);
_apiCache.Items.Count.ShouldBe(2);
@ -135,7 +137,7 @@ public class CachingResourceStoreTests
}
{
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo2", "foo1", "bar2", "bar1" });
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo2", "foo1", "bar2", "bar1" }, _ct);
items.Count().ShouldBe(2);
items.Select(x => x.Name).ShouldBe(["foo", "bar"]);
_apiCache.Items.Count.ShouldBe(2);
@ -147,7 +149,7 @@ public class CachingResourceStoreTests
_apiResourceNamesCache.Items.Clear();
_resourceCache.Items.Clear();
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo2", "foo1", "bar2", "bar1" });
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo2", "foo1", "bar2", "bar1" }, _ct);
items.Count().ShouldBe(2);
items.Select(x => x.Name).ShouldBe(["foo", "bar"]);
_apiCache.Items.Count.ShouldBe(2);
@ -160,7 +162,7 @@ public class CachingResourceStoreTests
_apiScopes.Clear();
_identityResources.Clear();
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo2", "foo1", "bar2", "bar1" });
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo2", "foo1", "bar2", "bar1" }, _ct);
items.Count().ShouldBe(2);
items.Select(x => x.Name).ShouldBe(["foo", "bar"]);
_apiCache.Items.Count.ShouldBe(2);
@ -179,12 +181,12 @@ public class CachingResourceStoreTests
_apiScopes.Add(new ApiScope("bar1"));
{
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo", "foo1", "bar", "bar1" });
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo", "foo1", "bar", "bar1" }, _ct);
items.Count().ShouldBe(2);
items.Select(x => x.Name).ShouldBe(["foo", "bar"], true);
}
{
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo", "foo1", "bar", "bar1" });
var items = await _subject.FindApiResourcesByScopeNameAsync(new[] { "foo", "foo1", "bar", "bar1" }, _ct);
items.Count().ShouldBe(2);
items.Select(x => x.Name).ShouldBe(["foo", "bar"]);
}