From 5df903ae444a0aec7df2b9ca7bc0bbdcbcd995b5 Mon Sep 17 00:00:00 2001 From: Damian Hickey Date: Fri, 20 Feb 2026 19:04:23 +0100 Subject: [PATCH] Add CT parameter to IResourceStore and IResourceStoreExtensions, flow through all implementations and tests --- .../UI/Main/Pages/Grants/Index.cshtml.cs | 2 +- .../Stores/ResourceStore.cs | 28 ++++++++------- .../Extensions/IResourceStoreExtensions.cs | 25 ++++++++----- .../Default/DiscoveryResponseGenerator.cs | 2 +- .../Default/UserInfoResponseGenerator.cs | 2 +- .../Stores/Caching/CachingResourceStore.cs | 35 ++++++++++--------- .../Stores/Empty/EmptyResourceStore.cs | 10 +++--- .../Stores/InMemory/InMemoryResourcesStore.cs | 10 +++--- .../Validation/Default/ApiSecretValidator.cs | 2 +- .../Default/DefaultResourceValidator.cs | 2 +- .../Default/TokenRequestValidator.cs | 4 +-- .../src/Storage/Stores/IResourceStore.cs | 19 +++++++--- .../Storage/Stores/ResourceStoreTests.cs | 20 ++++++----- .../Caches/ResourceStoreCacheTests.cs | 8 +++-- .../IResourceStoreExtensionsTests.cs | 30 ++++++++-------- ...yServerBuilderExtensionsCacheStoreTests.cs | 10 +++--- .../Default/CachingResourceStoreTests.cs | 30 ++++++++-------- 17 files changed, 134 insertions(+), 105 deletions(-) diff --git a/identity-server/hosts/UI/Main/Pages/Grants/Index.cshtml.cs b/identity-server/hosts/UI/Main/Pages/Grants/Index.cshtml.cs index 985616b00..337d628e6 100644 --- a/identity-server/hosts/UI/Main/Pages/Grants/Index.cshtml.cs +++ b/identity-server/hosts/UI/Main/Pages/Grants/Index.cshtml.cs @@ -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() { diff --git a/identity-server/src/EntityFramework.Storage/Stores/ResourceStore.cs b/identity-server/src/EntityFramework.Storage/Stores/ResourceStore.cs index c1b0012f4..9745966e0 100644 --- a/identity-server/src/EntityFramework.Storage/Stores/ResourceStore.cs +++ b/identity-server/src/EntityFramework.Storage/Stores/ResourceStore.cs @@ -52,8 +52,9 @@ public class ResourceStore : IResourceStore /// Finds the API resources by name. /// /// The names. + /// /// - public virtual async Task> FindApiResourcesByNameAsync(IEnumerable apiResourceNames) + public virtual async Task> FindApiResourcesByNameAsync(IEnumerable 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. /// /// + /// /// - public virtual async Task> FindApiResourcesByScopeNameAsync(IEnumerable scopeNames) + public virtual async Task> FindApiResourcesByScopeNameAsync(IEnumerable 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. /// /// + /// /// - public virtual async Task> FindIdentityResourcesByScopeNameAsync(IEnumerable scopeNames) + public virtual async Task> FindIdentityResourcesByScopeNameAsync(IEnumerable 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. /// /// + /// /// - public virtual async Task> FindApiScopesByNameAsync(IEnumerable scopeNames) + public virtual async Task> FindApiScopesByNameAsync(IEnumerable 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. /// /// - public virtual async Task GetAllResourcesAsync() + public virtual async Task 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", diff --git a/identity-server/src/IdentityServer/Extensions/IResourceStoreExtensions.cs b/identity-server/src/IdentityServer/Extensions/IResourceStoreExtensions.cs index d658dd740..0cbb6b9f4 100644 --- a/identity-server/src/IdentityServer/Extensions/IResourceStoreExtensions.cs +++ b/identity-server/src/IdentityServer/Extensions/IResourceStoreExtensions.cs @@ -16,12 +16,13 @@ public static class IResourceStoreExtensions /// /// The store. /// The scope names. + /// The used to propagate notifications that the operation should be cancelled. /// - public static async Task FindResourcesByScopeAsync(this IResourceStore store, IEnumerable scopeNames) + public static async Task FindResourcesByScopeAsync(this IResourceStore store, IEnumerable 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 /// /// The store. /// The scope names. + /// The used to propagate notifications that the operation should be cancelled. /// - public static async Task FindEnabledResourcesByScopeAsync(this IResourceStore store, IEnumerable scopeNames) => (await store.FindResourcesByScopeAsync(scopeNames)).FilterEnabled(); + public static async Task FindEnabledResourcesByScopeAsync(this IResourceStore store, IEnumerable scopeNames, CT ct) => (await store.FindResourcesByScopeAsync(scopeNames, ct)).FilterEnabled(); /// /// Gets all enabled resources. /// /// The store. + /// The used to propagate notifications that the operation should be cancelled. /// - public static async Task GetAllEnabledResourcesAsync(this IResourceStore store) + public static async Task 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 /// /// The store. /// The scope names. + /// The used to propagate notifications that the operation should be cancelled. /// - public static async Task> FindEnabledIdentityResourcesByScopeAsync(this IResourceStore store, IEnumerable scopeNames) => (await store.FindIdentityResourcesByScopeNameAsync(scopeNames)).Where(x => x.Enabled).ToArray(); + public static async Task> FindEnabledIdentityResourcesByScopeAsync(this IResourceStore store, IEnumerable scopeNames, CT ct) => (await store.FindIdentityResourcesByScopeNameAsync(scopeNames, ct)).Where(x => x.Enabled).ToArray(); /// /// Finds the enabled API resources by name. /// - public static async Task> FindEnabledApiResourcesByNameAsync(this IResourceStore store, IEnumerable resourceNames) => (await store.FindApiResourcesByNameAsync(resourceNames)).Where(x => x.Enabled).ToArray(); + /// The store. + /// The resource names. + /// The used to propagate notifications that the operation should be cancelled. + public static async Task> FindEnabledApiResourcesByNameAsync(this IResourceStore store, IEnumerable resourceNames, CT ct) => (await store.FindApiResourcesByNameAsync(resourceNames, ct)).Where(x => x.Enabled).ToArray(); } diff --git a/identity-server/src/IdentityServer/ResponseHandling/Default/DiscoveryResponseGenerator.cs b/identity-server/src/IdentityServer/ResponseHandling/Default/DiscoveryResponseGenerator.cs index c43b1a7de..ef05e64cf 100644 --- a/identity-server/src/IdentityServer/ResponseHandling/Default/DiscoveryResponseGenerator.cs +++ b/identity-server/src/IdentityServer/ResponseHandling/Default/DiscoveryResponseGenerator.cs @@ -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(); // scopes diff --git a/identity-server/src/IdentityServer/ResponseHandling/Default/UserInfoResponseGenerator.cs b/identity-server/src/IdentityServer/ResponseHandling/Default/UserInfoResponseGenerator.cs index d9f2002a4..ea1be9e23 100644 --- a/identity-server/src/IdentityServer/ResponseHandling/Default/UserInfoResponseGenerator.cs +++ b/identity-server/src/IdentityServer/ResponseHandling/Default/UserInfoResponseGenerator.cs @@ -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(), Enumerable.Empty()); var result = new ResourceValidationResult(resources); diff --git a/identity-server/src/IdentityServer/Stores/Caching/CachingResourceStore.cs b/identity-server/src/IdentityServer/Stores/Caching/CachingResourceStore.cs index bcfa37e7e..1dd697984 100644 --- a/identity-server/src/IdentityServer/Stores/Caching/CachingResourceStore.cs +++ b/identity-server/src/IdentityServer/Stores/Caching/CachingResourceStore.cs @@ -80,7 +80,7 @@ public class CachingResourceStore : IResourceStore } /// - public async Task GetAllResourcesAsync() + public async Task GetAllResourcesAsync(CT ct) { using var activity = Tracing.StoreActivitySource.StartActivity("CachingResourceStore.GetAllResources"); @@ -88,13 +88,13 @@ public class CachingResourceStore : IResourceStore var all = await _allCache.GetOrAddAsync(key, _options.Caching.ResourceStoreExpiration, - async () => await _inner.GetAllResourcesAsync()); + async () => await _inner.GetAllResourcesAsync(ct)); return all; } /// - public async Task> FindApiResourcesByScopeNameAsync(IEnumerable scopeNames) + public async Task> FindApiResourcesByScopeNameAsync(IEnumerable 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 : 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 : 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); } /// - public async Task> FindApiResourcesByNameAsync(IEnumerable apiResourceNames) + public async Task> FindApiResourcesByNameAsync(IEnumerable 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); } /// - public async Task> FindIdentityResourcesByScopeNameAsync(IEnumerable scopeNames) + public async Task> FindIdentityResourcesByScopeNameAsync(IEnumerable 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); } /// - public async Task> FindApiScopesByNameAsync(IEnumerable scopeNames) + public async Task> FindApiScopesByNameAsync(IEnumerable 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> FindItemsAsync( IEnumerable names, ICache cache, - Func, Task> getResourcesFunc, + Func, CT, Task> getResourcesFunc, Func> getFromResourcesFunc, Func getNameFunc, - string allCachePrefix + string allCachePrefix, + CT ct ) where TItem : class { @@ -237,7 +238,7 @@ public class CachingResourceStore : 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); diff --git a/identity-server/src/IdentityServer/Stores/Empty/EmptyResourceStore.cs b/identity-server/src/IdentityServer/Stores/Empty/EmptyResourceStore.cs index 236c73572..834918475 100644 --- a/identity-server/src/IdentityServer/Stores/Empty/EmptyResourceStore.cs +++ b/identity-server/src/IdentityServer/Stores/Empty/EmptyResourceStore.cs @@ -8,13 +8,13 @@ namespace Duende.IdentityServer.Stores.Empty; internal class EmptyResourceStore : IResourceStore { - public Task> FindApiResourcesByNameAsync(IEnumerable apiResourceNames) => Task.FromResult(Enumerable.Empty()); + public Task> FindApiResourcesByNameAsync(IEnumerable apiResourceNames, CT ct) => Task.FromResult(Enumerable.Empty()); - public Task> FindApiResourcesByScopeNameAsync(IEnumerable scopeNames) => Task.FromResult(Enumerable.Empty()); + public Task> FindApiResourcesByScopeNameAsync(IEnumerable scopeNames, CT ct) => Task.FromResult(Enumerable.Empty()); - public Task> FindApiScopesByNameAsync(IEnumerable scopeNames) => Task.FromResult(Enumerable.Empty()); + public Task> FindApiScopesByNameAsync(IEnumerable scopeNames, CT ct) => Task.FromResult(Enumerable.Empty()); - public Task> FindIdentityResourcesByScopeNameAsync(IEnumerable scopeNames) => Task.FromResult(Enumerable.Empty()); + public Task> FindIdentityResourcesByScopeNameAsync(IEnumerable scopeNames, CT ct) => Task.FromResult(Enumerable.Empty()); - public Task GetAllResourcesAsync() => Task.FromResult(new Resources() { OfflineAccess = true }); + public Task GetAllResourcesAsync(CT ct) => Task.FromResult(new Resources() { OfflineAccess = true }); } diff --git a/identity-server/src/IdentityServer/Stores/InMemory/InMemoryResourcesStore.cs b/identity-server/src/IdentityServer/Stores/InMemory/InMemoryResourcesStore.cs index ec45ec19a..74070c49e 100644 --- a/identity-server/src/IdentityServer/Stores/InMemory/InMemoryResourcesStore.cs +++ b/identity-server/src/IdentityServer/Stores/InMemory/InMemoryResourcesStore.cs @@ -45,7 +45,7 @@ public class InMemoryResourcesStore : IResourceStore } /// - public Task GetAllResourcesAsync() + public Task GetAllResourcesAsync(CT ct) { using var activity = Tracing.StoreActivitySource.StartActivity("InMemoryResourceStore.GetAllResources"); @@ -54,7 +54,7 @@ public class InMemoryResourcesStore : IResourceStore } /// - public Task> FindApiResourcesByNameAsync(IEnumerable apiResourceNames) + public Task> FindApiResourcesByNameAsync(IEnumerable apiResourceNames, CT ct) { ArgumentNullException.ThrowIfNull(apiResourceNames); using var activity = Tracing.StoreActivitySource.StartActivity("InMemoryResourceStore.FindApiResourcesByName"); @@ -67,7 +67,7 @@ public class InMemoryResourcesStore : IResourceStore } /// - public Task> FindIdentityResourcesByScopeNameAsync(IEnumerable scopeNames) + public Task> FindIdentityResourcesByScopeNameAsync(IEnumerable scopeNames, CT ct) { ArgumentNullException.ThrowIfNull(scopeNames); using var activity = Tracing.StoreActivitySource.StartActivity("InMemoryResourceStore.FindIdentityResourcesByScopeName"); @@ -81,7 +81,7 @@ public class InMemoryResourcesStore : IResourceStore } /// - public Task> FindApiResourcesByScopeNameAsync(IEnumerable scopeNames) + public Task> FindApiResourcesByScopeNameAsync(IEnumerable scopeNames, CT ct) { ArgumentNullException.ThrowIfNull(scopeNames); using var activity = Tracing.StoreActivitySource.StartActivity("InMemoryResourceStore.FindApiResourcesByScopeName"); @@ -95,7 +95,7 @@ public class InMemoryResourcesStore : IResourceStore } /// - public Task> FindApiScopesByNameAsync(IEnumerable scopeNames) + public Task> FindApiScopesByNameAsync(IEnumerable scopeNames, CT ct) { ArgumentNullException.ThrowIfNull(scopeNames); using var activity = Tracing.StoreActivitySource.StartActivity("InMemoryResourceStore.FindApiScopesByName"); diff --git a/identity-server/src/IdentityServer/Validation/Default/ApiSecretValidator.cs b/identity-server/src/IdentityServer/Validation/Default/ApiSecretValidator.cs index 459fd3b8f..0826b5334 100644 --- a/identity-server/src/IdentityServer/Validation/Default/ApiSecretValidator.cs +++ b/identity-server/src/IdentityServer/Validation/Default/ApiSecretValidator.cs @@ -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"); diff --git a/identity-server/src/IdentityServer/Validation/Default/DefaultResourceValidator.cs b/identity-server/src/IdentityServer/Validation/Default/DefaultResourceValidator.cs index ccd146123..0af93c407 100644 --- a/identity-server/src/IdentityServer/Validation/Default/DefaultResourceValidator.cs +++ b/identity-server/src/IdentityServer/Validation/Default/DefaultResourceValidator.cs @@ -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) { diff --git a/identity-server/src/IdentityServer/Validation/Default/TokenRequestValidator.cs b/identity-server/src/IdentityServer/Validation/Default/TokenRequestValidator.cs index 4d721af09..903a822c4 100644 --- a/identity-server/src/IdentityServer/Validation/Default/TokenRequestValidator.cs +++ b/identity-server/src/IdentityServer/Validation/Default/TokenRequestValidator.cs @@ -1106,12 +1106,12 @@ internal class TokenRequestValidator : ITokenRequestValidator var clientAllowedScopes = new List(); 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)); } diff --git a/identity-server/src/Storage/Stores/IResourceStore.cs b/identity-server/src/Storage/Stores/IResourceStore.cs index 5ad8d2b95..0ffa36e00 100644 --- a/identity-server/src/Storage/Stores/IResourceStore.cs +++ b/identity-server/src/Storage/Stores/IResourceStore.cs @@ -16,25 +16,34 @@ public interface IResourceStore /// /// Gets identity resources by scope name. /// - Task> FindIdentityResourcesByScopeNameAsync(IEnumerable scopeNames); + /// The scope names. + /// The used to propagate notifications that the operation should be cancelled. + Task> FindIdentityResourcesByScopeNameAsync(IEnumerable scopeNames, CT ct); /// /// Gets API scopes by scope name. /// - Task> FindApiScopesByNameAsync(IEnumerable scopeNames); + /// The scope names. + /// The used to propagate notifications that the operation should be cancelled. + Task> FindApiScopesByNameAsync(IEnumerable scopeNames, CT ct); /// /// Gets API resources by scope name. /// - Task> FindApiResourcesByScopeNameAsync(IEnumerable scopeNames); + /// The scope names. + /// The used to propagate notifications that the operation should be cancelled. + Task> FindApiResourcesByScopeNameAsync(IEnumerable scopeNames, CT ct); /// /// Gets API resources by API resource name. /// - Task> FindApiResourcesByNameAsync(IEnumerable apiResourceNames); + /// The API resource names. + /// The used to propagate notifications that the operation should be cancelled. + Task> FindApiResourcesByNameAsync(IEnumerable apiResourceNames, CT ct); /// /// Gets all resources. /// - Task GetAllResourcesAsync(); + /// The used to propagate notifications that the operation should be cancelled. + Task GetAllResourcesAsync(CT ct); } diff --git a/identity-server/test/IdentityServer.IntegrationTests/EntityFramework/Storage/Stores/ResourceStoreTests.cs b/identity-server/test/IdentityServer.IntegrationTests/EntityFramework/Storage/Stores/ResourceStoreTests.cs index f41e180df..4ab8b0566 100644 --- a/identity-server/test/IdentityServer.IntegrationTests/EntityFramework/Storage/Stores/ResourceStoreTests.cs +++ b/identity-server/test/IdentityServer.IntegrationTests/EntityFramework/Storage/Stores/ResourceStoreTests.cs @@ -16,6 +16,8 @@ namespace Duende.IdentityServer.IntegrationTests.EntityFramework.Storage.Stores; public class ScopeStoreTests : IntegrationTest { + private readonly CT _ct = TestContext.Current.CancellationToken; + public ScopeStoreTests(DatabaseProviderFixture fixture) : base(fixture) { foreach (var options in TestDatabaseProviders) @@ -76,7 +78,7 @@ public class ScopeStoreTests : IntegrationTest(), 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(), 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 { testApiScope.Name - }); + }, _ct); } resources.ShouldNotBeNull(); @@ -171,7 +173,7 @@ public class ScopeStoreTests : IntegrationTest(), 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 { resource.Name - })).ToList(); + }, _ct)).ToList(); } resources.ShouldNotBeNull(); @@ -228,7 +230,7 @@ public class ScopeStoreTests : IntegrationTest { resource.Name - })).ToList(); + }, _ct)).ToList(); } resources.ShouldNotBeNull(); @@ -254,7 +256,7 @@ public class ScopeStoreTests : IntegrationTest { resource.Name - })).ToList(); + }, _ct)).ToList(); } resources.ShouldNotBeNull(); @@ -285,7 +287,7 @@ public class ScopeStoreTests : IntegrationTest { resource.Name - })).ToList(); + }, _ct)).ToList(); } resources.ShouldNotBeNull(); @@ -329,7 +331,7 @@ public class ScopeStoreTests : IntegrationTest(), new NoneCancellationTokenProvider()); - resources = await store.GetAllResourcesAsync(); + resources = await store.GetAllResourcesAsync(_ct); } resources.ShouldNotBeNull(); diff --git a/identity-server/test/IdentityServer.UnitTests/Caches/ResourceStoreCacheTests.cs b/identity-server/test/IdentityServer.UnitTests/Caches/ResourceStoreCacheTests.cs index a8e96b4f7..7fc077318 100644 --- a/identity-server/test/IdentityServer.UnitTests/Caches/ResourceStoreCacheTests.cs +++ b/identity-server/test/IdentityServer.UnitTests/Caches/ResourceStoreCacheTests.cs @@ -13,6 +13,8 @@ namespace IdentityServer.UnitTests.Caches; public class ResourceStoreCacheTests { + private readonly CT _ct = TestContext.Current.CancellationToken; + private List _clients { get; set; } = new List(); private List _identityResources { get; set; } = new List(); private List _resources { get; set; } = new List(); @@ -55,7 +57,7 @@ public class ResourceStoreCacheTests var store = _provider.GetRequiredService(); 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(); 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(); 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"); diff --git a/identity-server/test/IdentityServer.UnitTests/Extensions/IResourceStoreExtensionsTests.cs b/identity-server/test/IdentityServer.UnitTests/Extensions/IResourceStoreExtensionsTests.cs index 0c183adf4..11671b251 100644 --- a/identity-server/test/IdentityServer.UnitTests/Extensions/IResourceStoreExtensionsTests.cs +++ b/identity-server/test/IdentityServer.UnitTests/Extensions/IResourceStoreExtensionsTests.cs @@ -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 a = () => store.GetAllEnabledResourcesAsync(); + Func a = () => store.GetAllEnabledResourcesAsync(_ct); var exception = await a.ShouldThrowAsync(); 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 a = () => store.GetAllEnabledResourcesAsync(); + Func a = () => store.GetAllEnabledResourcesAsync(_ct); var exception = await a.ShouldThrowAsync(); 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 a = () => store.FindResourcesByScopeAsync(new string[] { "A" }); + Func a = () => store.FindResourcesByScopeAsync(new string[] { "A" }, _ct); var exception = await a.ShouldThrowAsync(); 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 ApiResources { get; set; } = new List(); public List ApiScopes { get; set; } = new List(); - public Task> FindApiResourcesByNameAsync(IEnumerable names) + public Task> FindApiResourcesByNameAsync(IEnumerable 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> FindApiResourcesByScopeNameAsync(IEnumerable names) + public Task> FindApiResourcesByScopeNameAsync(IEnumerable names, CT ct) { ArgumentNullException.ThrowIfNull(names); @@ -165,7 +167,7 @@ public class IResourceStoreExtensionsTests return Task.FromResult(api); } - public Task> FindIdentityResourcesByScopeNameAsync(IEnumerable names) + public Task> FindIdentityResourcesByScopeNameAsync(IEnumerable names, CT ct) { ArgumentNullException.ThrowIfNull(names); @@ -176,7 +178,7 @@ public class IResourceStoreExtensionsTests return Task.FromResult(identity); } - public Task> FindApiScopesByNameAsync(IEnumerable scopeNames) + public Task> FindApiScopesByNameAsync(IEnumerable 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 GetAllResourcesAsync() + public Task GetAllResourcesAsync(CT ct) { var result = new Resources(IdentityResources, ApiResources, ApiScopes); return Task.FromResult(result); diff --git a/identity-server/test/IdentityServer.UnitTests/Extensions/IdentityServerBuilderExtensionsCacheStoreTests.cs b/identity-server/test/IdentityServer.UnitTests/Extensions/IdentityServerBuilderExtensionsCacheStoreTests.cs index 9a7c45117..1b873cfe0 100644 --- a/identity-server/test/IdentityServer.UnitTests/Extensions/IdentityServerBuilderExtensionsCacheStoreTests.cs +++ b/identity-server/test/IdentityServer.UnitTests/Extensions/IdentityServerBuilderExtensionsCacheStoreTests.cs @@ -22,15 +22,15 @@ public class IdentityServerBuilderExtensionsCacheStoreTests private class CustomResourceStore : IResourceStore { - public Task> FindIdentityResourcesByScopeNameAsync(IEnumerable scopeNames) => throw new System.NotImplementedException(); + public Task> FindIdentityResourcesByScopeNameAsync(IEnumerable scopeNames, CT ct) => throw new System.NotImplementedException(); - public Task> FindApiResourcesByScopeNameAsync(IEnumerable scopeNames) => throw new System.NotImplementedException(); + public Task> FindApiResourcesByScopeNameAsync(IEnumerable scopeNames, CT ct) => throw new System.NotImplementedException(); - public Task> FindApiResourcesByNameAsync(IEnumerable names) => throw new System.NotImplementedException(); + public Task> FindApiResourcesByNameAsync(IEnumerable names, CT ct) => throw new System.NotImplementedException(); - public Task GetAllResourcesAsync() => throw new System.NotImplementedException(); + public Task GetAllResourcesAsync(CT ct) => throw new System.NotImplementedException(); - public Task> FindApiScopesByNameAsync(IEnumerable scopeNames) => throw new System.NotImplementedException(); + public Task> FindApiScopesByNameAsync(IEnumerable scopeNames, CT ct) => throw new System.NotImplementedException(); } [Fact] diff --git a/identity-server/test/IdentityServer.UnitTests/Stores/Default/CachingResourceStoreTests.cs b/identity-server/test/IdentityServer.UnitTests/Stores/Default/CachingResourceStoreTests.cs index c71f44d43..6916ab087 100644 --- a/identity-server/test/IdentityServer.UnitTests/Stores/Default/CachingResourceStoreTests.cs +++ b/identity-server/test/IdentityServer.UnitTests/Stores/Default/CachingResourceStoreTests.cs @@ -11,6 +11,8 @@ namespace UnitTests.Stores.Default; public class CachingResourceStoreTests { + private readonly CT _ct = TestContext.Current.CancellationToken; + private List _identityResources = new List(); private List _apiResources = new List(); private List _apiScopes = new List(); @@ -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"]); }