feat: implement config builder, source, and extensions

This commit is contained in:
Stevan Freeborn
2026-04-02 13:52:58 -05:00
parent ca0a6dda87
commit 7c501fdf55
10 changed files with 1664 additions and 29 deletions
@@ -1,14 +1,83 @@
using Microsoft.Extensions.DependencyInjection;
using System.Text.Json.Serialization.Metadata;
using Microsoft.Extensions.Logging;
using StevanFreeborn.Extensions.Configuration.Secure.Cryptography;
using StevanFreeborn.Extensions.Configuration.Secure.Storage;
namespace StevanFreeborn.Extensions.Configuration.Secure.Configuration;
/// <summary>
/// Provides a builder interface for configuring secure configuration services.
/// Provides a fluent builder interface for configuring secure configuration storage and encryption.
/// </summary>
public interface ISecureConfigBuilder
{
/// <summary>
/// Gets the <see cref="IServiceCollection"/> used to register secure configuration services.
/// Configures JSON file-based storage using the provided options instance.
/// </summary>
IServiceCollection Services { get; }
/// <param name="options">The JSON storage configuration options.</param>
/// <returns>The current <see cref="ISecureConfigBuilder"/> instance for method chaining.</returns>
ISecureConfigBuilder UseJsonFileStorage(JsonStorageOptions options);
/// <summary>
/// Configures JSON file-based storage using an action to configure the options.
/// </summary>
/// <param name="configure">An action to configure the <see cref="JsonStorageOptions"/>.</param>
/// <returns>The current <see cref="ISecureConfigBuilder"/> instance for method chaining.</returns>
ISecureConfigBuilder UseJsonFileStorage(Action<JsonStorageOptions> configure);
/// <summary>
/// Registers a JSON AOT source-generated context for serializing complex types.
/// </summary>
/// <param name="context">A context that implements <see cref="IJsonTypeInfoResolver"/> that will be used for JSON serialization.</param>
/// <returns>The current <see cref="ISecureConfigBuilder"/> instance for method chaining.</returns>
ISecureConfigBuilder AddJsonAotContext(IJsonTypeInfoResolver context);
/// <summary>
/// Configures a custom storage provider for secure configuration data.
/// </summary>
/// <param name="provider">The custom storage provider implementation.</param>
/// <returns>The current <see cref="ISecureConfigBuilder"/> instance for method chaining.</returns>
ISecureConfigBuilder UseCustomStorage(ISecureStorageProvider provider);
/// <summary>
/// Configures encryption using a Base64-encoded encryption key.
/// </summary>
/// <param name="key">The Base64-encoded encryption key string.</param>
/// <returns>The current <see cref="ISecureConfigBuilder"/> instance for method chaining.</returns>
ISecureConfigBuilder WithBase64EncryptionKey(string key);
/// <summary>
/// Configures encryption using a key derived from the machine id.
/// </summary>
/// <returns>The current <see cref="ISecureConfigBuilder"/> instance for method chaining.</returns>
ISecureConfigBuilder WithMachineIdKey();
/// <summary>
/// Configures a custom encryption key provider.
/// </summary>
/// <param name="keyProvider">The custom encryption key provider implementation.</param>
/// <returns>The current <see cref="ISecureConfigBuilder"/> instance for method chaining.</returns>
ISecureConfigBuilder WithCustomKeyProvider(IEncryptionKeyProvider keyProvider);
/// <summary>
/// Configures logging using the provided logger factory.
/// </summary>
/// <param name="loggerFactory">The logger factory to use for logging operations.</param>
/// <returns>The current <see cref="ISecureConfigBuilder"/> instance for method chaining.</returns>
ISecureConfigBuilder WithLoggerFactory(ILoggerFactory loggerFactory);
/// <summary>
/// Configures AES crypto provider for encryption and decryption
/// </summary>
/// <returns>The current <see cref="ISecureConfigBuilder"/> instance for method chaining.</returns>
ISecureConfigBuilder WithAesCryptoProvider();
/// <summary>
/// Configures the factory function that will be used to create the crypto provider for encryption and decryption
/// </summary>
/// <param name="cryptoProviderFactory">The crypto provider factor to use for encryption and decryption operations.</param>
/// <returns>The current <see cref="ISecureConfigBuilder"/> instance for method chaining.</returns>
ISecureConfigBuilder WithCustomCryptoProvider(Func<IEncryptionKeyProvider, ICryptoProvider> cryptoProviderFactory);
}
@@ -0,0 +1,103 @@
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using StevanFreeborn.Extensions.Configuration.Secure.Cryptography;
using StevanFreeborn.Extensions.Configuration.Secure.Storage;
namespace StevanFreeborn.Extensions.Configuration.Secure.Configuration;
internal sealed class SecureConfigBuilder : ISecureConfigBuilder
{
internal ISecureStorageProvider? StorageProvider { get; private set; }
internal Func<IEncryptionKeyProvider, ICryptoProvider>? CryptoProviderFactory { get; private set; }
internal IEncryptionKeyProvider? KeyProvider { get; private set; }
internal ILoggerFactory LoggerFactory { get; private set; } = NullLoggerFactory.Instance;
internal JsonSerializerOptions SerializerOptions { get; } = new()
{
PropertyNameCaseInsensitive = true,
};
public ISecureConfigBuilder UseJsonFileStorage(JsonStorageOptions options)
{
StorageProvider = new JsonFileStorageProvider(options);
return this;
}
public ISecureConfigBuilder UseJsonFileStorage(Action<JsonStorageOptions> configure)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(configure);
#else
if (configure is null)
{
throw new ArgumentNullException(nameof(configure));
}
#endif
var options = new JsonStorageOptions();
configure.Invoke(options);
StorageProvider = new JsonFileStorageProvider(options);
return this;
}
public ISecureConfigBuilder AddJsonAotContext(IJsonTypeInfoResolver context)
{
#if NET6_0_OR_GREATER
ArgumentNullException.ThrowIfNull(context);
#else
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
#endif
SerializerOptions.TypeInfoResolverChain.Insert(0, context);
return this;
}
public ISecureConfigBuilder UseCustomStorage(ISecureStorageProvider provider)
{
StorageProvider = provider ?? throw new ArgumentNullException(nameof(provider));
return this;
}
public ISecureConfigBuilder WithBase64EncryptionKey(string key)
{
KeyProvider = new StaticKeyProvider(key);
return this;
}
public ISecureConfigBuilder WithMachineIdKey()
{
var logger = LoggerFactory.CreateLogger<MachineIdKeyGenerator>();
KeyProvider = new MachineIdKeyProvider(new MachineIdKeyGenerator(logger));
return this;
}
public ISecureConfigBuilder WithCustomKeyProvider(IEncryptionKeyProvider provider)
{
KeyProvider = provider ?? throw new ArgumentNullException(nameof(provider));
return this;
}
public ISecureConfigBuilder WithLoggerFactory(ILoggerFactory loggerFactory)
{
LoggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
return this;
}
public ISecureConfigBuilder WithAesCryptoProvider()
{
CryptoProviderFactory = (kp) => new AesCryptoProvider(kp);
return this;
}
public ISecureConfigBuilder WithCustomCryptoProvider(Func<IEncryptionKeyProvider, ICryptoProvider> cryptoProviderFactory)
{
CryptoProviderFactory = cryptoProviderFactory;
return this;
}
}
@@ -0,0 +1,29 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using StevanFreeborn.Extensions.Configuration.Secure.Cryptography;
using StevanFreeborn.Extensions.Configuration.Secure.Storage;
namespace StevanFreeborn.Extensions.Configuration.Secure.Configuration;
internal sealed class SecureConfigSource(
ISecureStorageProvider storageProvider,
ICryptoProvider cryptoProvider,
ILoggerFactory loggerFactory
) : IConfigurationSource
{
private readonly ISecureStorageProvider _storageProvider = storageProvider
?? throw new ArgumentNullException(nameof(storageProvider));
private readonly ICryptoProvider _cryptoProvider = cryptoProvider
?? throw new ArgumentNullException(nameof(cryptoProvider));
private readonly ILoggerFactory _loggerFactory = loggerFactory
?? throw new ArgumentNullException(nameof(loggerFactory));
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
var logger = _loggerFactory.CreateLogger<SecureConfigProvider>();
return new SecureConfigProvider(_storageProvider, _cryptoProvider, logger);
}
}