1
0
mirror of https://github.com/bitwarden/server synced 2026-01-02 08:33:48 +00:00
Files
server/src/Identity/IdentityServer/DynamicClientStore.cs
Ike 43d753dcb1 [PM-20592] [PM-22737] [PM-22738] Send grant validator (#6151)
**feat**: create `SendGrantValidator` and initial `SendPasswordValidator` for Send access grants  
**feat**: add feature flag to toggle Send grant validation logic  
**feat**: add Send client to Identity and update `ApiClient` to generic `Client`  
**feat**: register Send services in DI pipeline  
**feat**: add claims management support to `ProfileService`  
**feat**: distinguish between invalid grant and invalid request in `SendAccessGrantValidator`

**fix**: update parsing of `send_id` from request  
**fix**: add early return when feature flag is disabled  
**fix**: rename and organize Send access scope and grant type  
**fix**: dotnet format

**test**: add unit and integration tests for `SendGrantValidator`  
**test**: update OpenID configuration and API resource claims

**doc**: move documentation to interfaces and update inline comments  

**chore**: add TODO for future support of `CustomGrantTypes`
2025-08-13 18:38:00 -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.Clients.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);
}
}