feat: implement config builder, source, and extensions
This commit is contained in:
+73
-4
@@ -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);
|
||||
}
|
||||
+103
@@ -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;
|
||||
}
|
||||
}
|
||||
+29
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
using System.Text.Json;
|
||||
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
using StevanFreeborn.Extensions.Configuration.Secure.Configuration;
|
||||
using StevanFreeborn.Extensions.Configuration.Secure.Cryptography;
|
||||
using StevanFreeborn.Extensions.Configuration.Secure.Storage;
|
||||
|
||||
namespace StevanFreeborn.Extensions.Configuration.Secure;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for configuring secure configuration in .NET applications.
|
||||
/// </summary>
|
||||
public static class SecureConfigExtensions
|
||||
{
|
||||
private const string JsonSerializerOptionsKey = "SecureConfigJsonSerializerOptions";
|
||||
|
||||
/// <summary>
|
||||
/// Adds secure configuration to the configuration builder.
|
||||
/// </summary>
|
||||
/// <param name="builder">The configuration builder to add secure configuration to.</param>
|
||||
/// <param name="configure">An action to configure the secure configuration builder.</param>
|
||||
/// <returns>The configuration builder with secure configuration added.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="builder"/> or <paramref name="configure"/> is null.</exception>
|
||||
/// <exception cref="InvalidOperationException">Thrown when a required provider is not configured.</exception>
|
||||
public static IConfigurationBuilder AddSecureConfig(
|
||||
this IConfigurationBuilder builder,
|
||||
Action<ISecureConfigBuilder> configure
|
||||
)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
ArgumentNullException.ThrowIfNull(configure);
|
||||
#else
|
||||
if (builder is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
if (configure is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configure));
|
||||
}
|
||||
#endif
|
||||
|
||||
var configBuilder = new SecureConfigBuilder();
|
||||
|
||||
configure.Invoke(configBuilder);
|
||||
|
||||
if (configBuilder.StorageProvider is null)
|
||||
{
|
||||
throw new InvalidOperationException("A storage provider must be configured.");
|
||||
}
|
||||
|
||||
if (configBuilder.KeyProvider is null)
|
||||
{
|
||||
throw new InvalidOperationException("A key provider must be configured");
|
||||
}
|
||||
|
||||
if (configBuilder.CryptoProviderFactory is null)
|
||||
{
|
||||
throw new InvalidOperationException("A crypto provider must be configured.");
|
||||
}
|
||||
|
||||
var cryptoProvider = configBuilder.CryptoProviderFactory.Invoke(configBuilder.KeyProvider);
|
||||
|
||||
var source = new SecureConfigSource(configBuilder.StorageProvider, cryptoProvider, configBuilder.LoggerFactory);
|
||||
return builder.Add(source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds secure configuration services to the service collection.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection to add secure configuration services to.</param>
|
||||
/// <param name="configure">An action to configure the secure configuration builder.</param>
|
||||
/// <returns>The service collection with secure configuration services added.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <paramref name="services"/> or <paramref name="configure"/> is null.</exception>
|
||||
/// <exception cref="InvalidOperationException">Thrown when a required provider is not configured.</exception>
|
||||
public static IServiceCollection AddSecureConfig(
|
||||
this IServiceCollection services,
|
||||
Action<ISecureConfigBuilder> configure
|
||||
)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configure);
|
||||
#else
|
||||
if (services is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
if (configure is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configure));
|
||||
}
|
||||
#endif
|
||||
|
||||
var configBuilder = new SecureConfigBuilder();
|
||||
|
||||
configure(configBuilder);
|
||||
|
||||
if (configBuilder.StorageProvider is null)
|
||||
{
|
||||
throw new InvalidOperationException("A storage provider must be configured.");
|
||||
}
|
||||
|
||||
if (configBuilder.KeyProvider is null)
|
||||
{
|
||||
throw new InvalidOperationException("A key provider must be configured");
|
||||
}
|
||||
|
||||
if (configBuilder.CryptoProviderFactory is null)
|
||||
{
|
||||
throw new InvalidOperationException("A crypto provider must be configured.");
|
||||
}
|
||||
|
||||
services.TryAddKeyedSingleton(JsonSerializerOptionsKey, configBuilder.SerializerOptions);
|
||||
services.TryAddSingleton(configBuilder.StorageProvider);
|
||||
services.TryAddSingleton(configBuilder.KeyProvider);
|
||||
services.TryAddSingleton(configBuilder.CryptoProviderFactory.Invoke(configBuilder.KeyProvider));
|
||||
services.TryAddSingleton<ISecureConfig>(sp =>
|
||||
{
|
||||
var storageProvider = sp.GetRequiredService<ISecureStorageProvider>();
|
||||
var cryptoProvider = sp.GetRequiredService<ICryptoProvider>();
|
||||
var serializerOptions = sp.GetRequiredKeyedService<JsonSerializerOptions>(JsonSerializerOptionsKey);
|
||||
return new SecureConfig(storageProvider, cryptoProvider, serializerOptions);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user