mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
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>
This commit is contained in:
@ -9,7 +9,6 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.Platform.Installations;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Test.Auth.AutoFixture;
|
||||
using Bit.Identity.IdentityServer;
|
||||
using Bit.IntegrationTestCommon.Factories;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
|
@ -0,0 +1,75 @@
|
||||
using Bit.Core.IdentityServer;
|
||||
using Bit.Core.Platform.Installations;
|
||||
using Bit.Identity.IdentityServer.ClientProviders;
|
||||
using IdentityModel;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Identity.Test.IdentityServer.ClientProviders;
|
||||
|
||||
public class InstallationClientProviderTests
|
||||
{
|
||||
|
||||
private readonly IInstallationRepository _installationRepository;
|
||||
private readonly InstallationClientProvider _sut;
|
||||
|
||||
public InstallationClientProviderTests()
|
||||
{
|
||||
_installationRepository = Substitute.For<IInstallationRepository>();
|
||||
|
||||
_sut = new InstallationClientProvider(_installationRepository);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAsync_NonGuidIdentifier_ReturnsNull()
|
||||
{
|
||||
var installationClient = await _sut.GetAsync("non-guid");
|
||||
|
||||
Assert.Null(installationClient);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAsync_NonExistingInstallationGuid_ReturnsNull()
|
||||
{
|
||||
var installationClient = await _sut.GetAsync(Guid.NewGuid().ToString());
|
||||
|
||||
Assert.Null(installationClient);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task GetAsync_ExistingClient_ReturnsClientRespectingEnabledStatus(bool enabled)
|
||||
{
|
||||
var installationId = Guid.NewGuid();
|
||||
|
||||
_installationRepository
|
||||
.GetByIdAsync(installationId)
|
||||
.Returns(new Installation
|
||||
{
|
||||
Id = installationId,
|
||||
Key = "some-key",
|
||||
Email = "some-email",
|
||||
Enabled = enabled,
|
||||
});
|
||||
|
||||
var installationClient = await _sut.GetAsync(installationId.ToString());
|
||||
|
||||
Assert.NotNull(installationClient);
|
||||
Assert.Equal($"installation.{installationId}", installationClient.ClientId);
|
||||
Assert.True(installationClient.RequireClientSecret);
|
||||
// The usage of this secret is tested in integration tests
|
||||
Assert.Single(installationClient.ClientSecrets);
|
||||
Assert.Collection(
|
||||
installationClient.AllowedScopes,
|
||||
s => Assert.Equal(ApiScopes.ApiPush, s),
|
||||
s => Assert.Equal(ApiScopes.ApiLicensing, s),
|
||||
s => Assert.Equal(ApiScopes.ApiInstallation, s)
|
||||
);
|
||||
Assert.Equal(enabled, installationClient.Enabled);
|
||||
Assert.Equal(TimeSpan.FromDays(1).TotalSeconds, installationClient.AccessTokenLifetime);
|
||||
var claim = Assert.Single(installationClient.Claims);
|
||||
Assert.Equal(JwtClaimTypes.Subject, claim.Type);
|
||||
Assert.Equal(installationId.ToString(), claim.Value);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
using Bit.Core.IdentityServer;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Identity.IdentityServer.ClientProviders;
|
||||
using IdentityModel;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Identity.Test.IdentityServer.ClientProviders;
|
||||
|
||||
public class InternalClientProviderTests
|
||||
{
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
private readonly InternalClientProvider _sut;
|
||||
|
||||
public InternalClientProviderTests()
|
||||
{
|
||||
_globalSettings = new GlobalSettings
|
||||
{
|
||||
SelfHosted = true,
|
||||
};
|
||||
_sut = new InternalClientProvider(_globalSettings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetAsync_ReturnsInternalClient()
|
||||
{
|
||||
var internalClient = await _sut.GetAsync("blah");
|
||||
|
||||
Assert.NotNull(internalClient);
|
||||
Assert.Equal($"internal.blah", internalClient.ClientId);
|
||||
Assert.True(internalClient.RequireClientSecret);
|
||||
var secret = Assert.Single(internalClient.ClientSecrets);
|
||||
Assert.NotNull(secret);
|
||||
Assert.NotNull(secret.Value);
|
||||
var scope = Assert.Single(internalClient.AllowedScopes);
|
||||
Assert.Equal(ApiScopes.Internal, scope);
|
||||
Assert.Equal(TimeSpan.FromDays(1).TotalSeconds, internalClient.AccessTokenLifetime);
|
||||
Assert.True(internalClient.Enabled);
|
||||
var claim = Assert.Single(internalClient.Claims);
|
||||
Assert.Equal(JwtClaimTypes.Subject, claim.Type);
|
||||
Assert.Equal("blah", claim.Value);
|
||||
}
|
||||
}
|
148
test/Identity.Test/IdentityServer/DynamicClientStoreTests.cs
Normal file
148
test/Identity.Test/IdentityServer/DynamicClientStoreTests.cs
Normal file
@ -0,0 +1,148 @@
|
||||
using Bit.Identity.IdentityServer;
|
||||
using Duende.IdentityServer.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Identity.Test.IdentityServer;
|
||||
|
||||
public class DynamicClientStoreTests
|
||||
{
|
||||
private readonly IServiceCollection _services;
|
||||
private readonly IClientProvider _apiKeyProvider;
|
||||
|
||||
private readonly Func<DynamicClientStore> _sutCreator;
|
||||
|
||||
public DynamicClientStoreTests()
|
||||
{
|
||||
_services = new ServiceCollection();
|
||||
_apiKeyProvider = Substitute.For<IClientProvider>();
|
||||
|
||||
_sutCreator = () => new DynamicClientStore(
|
||||
_services.BuildServiceProvider(),
|
||||
_apiKeyProvider,
|
||||
new StaticClientStore(new Core.Settings.GlobalSettings())
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("mobile")]
|
||||
[InlineData("web")]
|
||||
[InlineData("browser")]
|
||||
[InlineData("desktop")]
|
||||
[InlineData("cli")]
|
||||
[InlineData("connector")]
|
||||
public async Task FindClientByIdAsync_StaticClients_Works(string staticClientId)
|
||||
{
|
||||
var sut = _sutCreator();
|
||||
|
||||
var client = await sut.FindClientByIdAsync(staticClientId);
|
||||
|
||||
Assert.NotNull(client);
|
||||
Assert.Equal(staticClientId, client.ClientId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FindClientByIdAsync_SplitName_NoService_ReturnsNull()
|
||||
{
|
||||
_services.AddClientProvider<FakeClientProvider>("my-provider");
|
||||
|
||||
var sut = _sutCreator();
|
||||
|
||||
var client = await sut.FindClientByIdAsync("blah.something");
|
||||
|
||||
Assert.Null(client);
|
||||
|
||||
await _apiKeyProvider
|
||||
.Received(0)
|
||||
.GetAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task FindClientByIdAsync_SplitName_HasService_ReturnsValueFromService(bool returnNull)
|
||||
{
|
||||
var fakeProvider = Substitute.For<IClientProvider>();
|
||||
|
||||
fakeProvider
|
||||
.GetAsync("something")
|
||||
.Returns(returnNull ? null : new Client { ClientId = "fake" });
|
||||
|
||||
_services.AddKeyedSingleton("my-provider", fakeProvider);
|
||||
|
||||
var sut = _sutCreator();
|
||||
|
||||
var client = await sut.FindClientByIdAsync("my-provider.something");
|
||||
|
||||
if (returnNull)
|
||||
{
|
||||
Assert.Null(client);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.NotNull(client);
|
||||
}
|
||||
|
||||
await fakeProvider
|
||||
.Received(1)
|
||||
.GetAsync("something");
|
||||
|
||||
await _apiKeyProvider
|
||||
.Received(0)
|
||||
.GetAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task FindClientByIdAsync_RandomString_NotSplit_TriesApiKey(bool returnsNull)
|
||||
{
|
||||
_apiKeyProvider
|
||||
.GetAsync("random-string")
|
||||
.Returns(returnsNull ? null : new Client { ClientId = "test" });
|
||||
|
||||
var sut = _sutCreator();
|
||||
|
||||
var client = await sut.FindClientByIdAsync("random-string");
|
||||
|
||||
if (returnsNull)
|
||||
{
|
||||
Assert.Null(client);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.NotNull(client);
|
||||
}
|
||||
|
||||
await _apiKeyProvider
|
||||
.Received(1)
|
||||
.GetAsync("random-string");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("id.")]
|
||||
[InlineData("id. ")]
|
||||
public async Task FindClientByIdAsync_InvalidIdentifierValue_ReturnsNull(string clientId)
|
||||
{
|
||||
var sut = _sutCreator();
|
||||
|
||||
var client = await sut.FindClientByIdAsync(clientId);
|
||||
Assert.Null(client);
|
||||
}
|
||||
|
||||
private class FakeClientProvider : IClientProvider
|
||||
{
|
||||
public FakeClientProvider()
|
||||
{
|
||||
Fake = Substitute.For<IClientProvider>();
|
||||
}
|
||||
|
||||
public IClientProvider Fake { get; }
|
||||
|
||||
public Task<Client?> GetAsync(string identifier)
|
||||
{
|
||||
return Fake.GetAsync(identifier);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user