1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-05 18:50:35 -05:00
bitwarden/src/Identity/IdentityServer/DynamicClientStore.cs
Justin Baur 0b2b573bd3
Add DynamicClientStore (#5670)
* Add DynamicClientStore

* Formatting

* Fix Debug assertion

* Make Identity internals visible to its unit tests

* Add installation client provider tests

* Add internal client provider tests

* Add DynamicClientStore tests

* Fix namespaces after merge

* Format

* Add docs and remove TODO comments

* Use preferred prefix for API keys

---------

Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com>
2025-05-30 12:58:54 -04:00

76 lines
2.4 KiB
C#

#nullable enable
using Bit.Identity.IdentityServer.ClientProviders;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Stores;
namespace Bit.Identity.IdentityServer;
public interface IClientProvider
{
Task<Client?> GetAsync(string identifier);
}
internal class DynamicClientStore : IClientStore
{
private readonly IServiceProvider _serviceProvider;
private readonly IClientProvider _apiKeyClientProvider;
private readonly StaticClientStore _staticClientStore;
public DynamicClientStore(
IServiceProvider serviceProvider,
[FromKeyedServices(SecretsManagerApiKeyProvider.ApiKeyPrefix)] IClientProvider apiKeyClientProvider,
StaticClientStore staticClientStore
)
{
_serviceProvider = serviceProvider;
_apiKeyClientProvider = apiKeyClientProvider;
_staticClientStore = staticClientStore;
}
public Task<Client?> FindClientByIdAsync(string clientId)
{
var clientIdSpan = clientId.AsSpan();
var firstPeriod = clientIdSpan.IndexOf('.');
if (firstPeriod == -1)
{
// No splitter, attempt but don't fail for a static client
if (_staticClientStore.ApiClients.TryGetValue(clientId, out var client))
{
return Task.FromResult<Client?>(client);
}
}
else
{
// Increment past the period
var identifierName = clientIdSpan[..firstPeriod++];
var identifier = clientIdSpan[firstPeriod..];
// The identifier is required to be non-empty
if (identifier.IsEmpty || identifier.IsWhiteSpace())
{
return Task.FromResult<Client?>(null);
}
// Once identifierName is proven valid, materialize the string
var clientBuilder = _serviceProvider.GetKeyedService<IClientProvider>(identifierName.ToString());
if (clientBuilder == null)
{
// No client registered by this identifier
return Task.FromResult<Client?>(null);
}
return clientBuilder.GetAsync(identifier.ToString());
}
// It could be an ApiKey, give them the full thing to try,
// this is a special case for legacy reasons, no other client should
// be allowed without a prefixing identifier.
return _apiKeyClientProvider.GetAsync(clientId);
}
}