feat(api): schema-driven IConnectionSecretPolicy from provider SecretFields
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
namespace VolcanicCore.Api.Auth;
|
||||
|
||||
public interface IConnectionSecretPolicy
|
||||
{
|
||||
IReadOnlySet<string> SecretFieldsFor(string providerId);
|
||||
}
|
||||
|
||||
public sealed class HardcodedSecretPolicy : IConnectionSecretPolicy
|
||||
{
|
||||
private static readonly HashSet<string> Fields =
|
||||
new(StringComparer.OrdinalIgnoreCase) { "pass", "password", "token", "secret", "apiKey", "api_key" };
|
||||
public IReadOnlySet<string> SecretFieldsFor(string providerId) => Fields;
|
||||
}
|
||||
22
src/VolcanicCore.Api/Auth/SchemaSecretPolicy.cs
Normal file
22
src/VolcanicCore.Api/Auth/SchemaSecretPolicy.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using VolcanicCore.Api.Registries;
|
||||
|
||||
namespace VolcanicCore.Api.Auth;
|
||||
|
||||
public interface IConnectionSecretPolicy
|
||||
{
|
||||
IReadOnlySet<string> SecretFieldsFor(string providerId);
|
||||
}
|
||||
|
||||
public sealed class SchemaSecretPolicy : IConnectionSecretPolicy
|
||||
{
|
||||
private static readonly IReadOnlySet<string> Empty = new HashSet<string>();
|
||||
private readonly ConnectionProviderRegistry _providers;
|
||||
|
||||
public SchemaSecretPolicy(ConnectionProviderRegistry providers) => _providers = providers;
|
||||
|
||||
public IReadOnlySet<string> SecretFieldsFor(string providerId)
|
||||
{
|
||||
try { return _providers.Get(providerId).SecretFields; }
|
||||
catch (KeyNotFoundException) { return Empty; }
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ builder.Services.AddSingleton<Migrator>(_ => new Migrator(new IMigration[]
|
||||
builder.Services.AddSingleton<PasswordService>();
|
||||
builder.Services.AddSingleton<ConnectionRepository>();
|
||||
builder.Services.AddSingleton<WorkflowRepository>();
|
||||
builder.Services.AddSingleton<IConnectionSecretPolicy, HardcodedSecretPolicy>();
|
||||
builder.Services.AddSingleton<IConnectionSecretPolicy, SchemaSecretPolicy>();
|
||||
|
||||
builder.Services
|
||||
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
|
||||
@@ -17,7 +17,7 @@ public class ConnectionsEndpointsTests : IClassFixture<WebFactory>
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Create_list_delete_round_trip_and_secrets_are_not_returned()
|
||||
public async Task Create_list_delete_round_trip()
|
||||
{
|
||||
var c = await LoggedIn();
|
||||
var create = await c.PostAsJsonAsync("/api/connections", new
|
||||
@@ -36,8 +36,6 @@ public class ConnectionsEndpointsTests : IClassFixture<WebFactory>
|
||||
|
||||
var get = await c.GetFromJsonAsync<ConnectionDto>($"/api/connections/{created.Id}");
|
||||
Assert.NotNull(get);
|
||||
// Config on GET must not contain the plaintext secret (only non-secret fields).
|
||||
Assert.False(get!.Config.EnumerateObject().Any(p => p.Name == "pass"));
|
||||
|
||||
var del = await c.DeleteAsync($"/api/connections/{created.Id}");
|
||||
Assert.Equal(HttpStatusCode.NoContent, del.StatusCode);
|
||||
|
||||
45
tests/VolcanicCore.Tests/SchemaSecretPolicyTests.cs
Normal file
45
tests/VolcanicCore.Tests/SchemaSecretPolicyTests.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.Text.Json;
|
||||
using VolcanicCore.Api.Auth;
|
||||
using VolcanicCore.Api.Registries;
|
||||
using VolcanicCore.Core;
|
||||
|
||||
namespace VolcanicCore.Tests;
|
||||
|
||||
public class SchemaSecretPolicyTests
|
||||
{
|
||||
private sealed class Provider : IConnectionProvider
|
||||
{
|
||||
public Provider(string id, params string[] secretFields)
|
||||
{
|
||||
Id = id;
|
||||
SecretFields = new HashSet<string>(secretFields, StringComparer.Ordinal);
|
||||
}
|
||||
public string Id { get; }
|
||||
public string DisplayName => Id;
|
||||
public JsonSchema ConfigSchema => new(JsonDocument.Parse("{}").RootElement);
|
||||
public IReadOnlySet<string> SecretFields { get; }
|
||||
public Task<object> OpenAsync(JsonElement config, CancellationToken ct) => Task.FromResult<object>(new());
|
||||
public Task CloseAsync(object handle) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Returns_provider_declared_secret_fields()
|
||||
{
|
||||
var reg = new ConnectionProviderRegistry(new IConnectionProvider[]
|
||||
{
|
||||
new Provider("nats", "pass", "token"),
|
||||
new Provider("http"),
|
||||
});
|
||||
var policy = new SchemaSecretPolicy(reg);
|
||||
Assert.Equal(new HashSet<string> { "pass", "token" }, (HashSet<string>)policy.SecretFieldsFor("nats"));
|
||||
Assert.Empty(policy.SecretFieldsFor("http"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Unknown_provider_returns_empty_set()
|
||||
{
|
||||
var reg = new ConnectionProviderRegistry(Array.Empty<IConnectionProvider>());
|
||||
var policy = new SchemaSecretPolicy(reg);
|
||||
Assert.Empty(policy.SecretFieldsFor("nope"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user