85 lines
3.5 KiB
C#
85 lines
3.5 KiB
C#
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using StellaOps.Authority.Plugins.Abstractions;
|
|
using StellaOps.Authority.Plugin.Standard.Storage;
|
|
using StellaOps.Authority.Storage.Mongo.Documents;
|
|
using StellaOps.Authority.Storage.Mongo.Stores;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Authority.Plugin.Standard.Tests;
|
|
|
|
public class StandardClientProvisioningStoreTests
|
|
{
|
|
[Fact]
|
|
public async Task CreateOrUpdateAsync_HashesSecretAndPersistsDocument()
|
|
{
|
|
var store = new TrackingClientStore();
|
|
var revocations = new TrackingRevocationStore();
|
|
var provisioning = new StandardClientProvisioningStore("standard", store, revocations, TimeProvider.System);
|
|
|
|
var registration = new AuthorityClientRegistration(
|
|
clientId: "bootstrap-client",
|
|
confidential: true,
|
|
displayName: "Bootstrap",
|
|
clientSecret: "SuperSecret1!",
|
|
allowedGrantTypes: new[] { "client_credentials" },
|
|
allowedScopes: new[] { "scopeA" });
|
|
|
|
var result = await provisioning.CreateOrUpdateAsync(registration, CancellationToken.None);
|
|
|
|
Assert.True(result.Succeeded);
|
|
Assert.True(store.Documents.TryGetValue("bootstrap-client", out var document));
|
|
Assert.NotNull(document);
|
|
Assert.Equal(AuthoritySecretHasher.ComputeHash("SuperSecret1!"), document!.SecretHash);
|
|
Assert.Equal("standard", document.Plugin);
|
|
|
|
var descriptor = await provisioning.FindByClientIdAsync("bootstrap-client", CancellationToken.None);
|
|
Assert.NotNull(descriptor);
|
|
Assert.Equal("bootstrap-client", descriptor!.ClientId);
|
|
Assert.True(descriptor.Confidential);
|
|
Assert.Contains("client_credentials", descriptor.AllowedGrantTypes);
|
|
Assert.Contains("scopeA", descriptor.AllowedScopes);
|
|
}
|
|
|
|
private sealed class TrackingClientStore : IAuthorityClientStore
|
|
{
|
|
public Dictionary<string, AuthorityClientDocument> Documents { get; } = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
public ValueTask<AuthorityClientDocument?> FindByClientIdAsync(string clientId, CancellationToken cancellationToken)
|
|
{
|
|
Documents.TryGetValue(clientId, out var document);
|
|
return ValueTask.FromResult(document);
|
|
}
|
|
|
|
public ValueTask UpsertAsync(AuthorityClientDocument document, CancellationToken cancellationToken)
|
|
{
|
|
Documents[document.ClientId] = document;
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
|
|
public ValueTask<bool> DeleteByClientIdAsync(string clientId, CancellationToken cancellationToken)
|
|
{
|
|
var removed = Documents.Remove(clientId);
|
|
return ValueTask.FromResult(removed);
|
|
}
|
|
}
|
|
|
|
private sealed class TrackingRevocationStore : IAuthorityRevocationStore
|
|
{
|
|
public List<AuthorityRevocationDocument> Upserts { get; } = new();
|
|
|
|
public ValueTask UpsertAsync(AuthorityRevocationDocument document, CancellationToken cancellationToken)
|
|
{
|
|
Upserts.Add(document);
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
|
|
public ValueTask<bool> RemoveAsync(string category, string revocationId, CancellationToken cancellationToken)
|
|
=> ValueTask.FromResult(true);
|
|
|
|
public ValueTask<IReadOnlyList<AuthorityRevocationDocument>> GetActiveAsync(DateTimeOffset asOf, CancellationToken cancellationToken)
|
|
=> ValueTask.FromResult<IReadOnlyList<AuthorityRevocationDocument>>(Array.Empty<AuthorityRevocationDocument>());
|
|
}
|
|
}
|