refactor(securestore): store entire connection strings in SecureStore
Eliminates placeholder substitution (${KEY}) in favor of storing complete
connection strings as single encrypted values. SecureStore now auto-creates
entries for all connection strings defined in appsettings. ConfigManager
editor reads/writes values directly to SecureStore.
This commit is contained in:
@@ -31,4 +31,10 @@ public class SecureStoreOptions
|
||||
/// List of secret keys that must exist in the store for the application to start.
|
||||
/// </summary>
|
||||
public List<string> RequiredKeys { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// List of connection string names that must exist in the store.
|
||||
/// Populated automatically from the ConnectionStrings configuration section.
|
||||
/// </summary>
|
||||
public List<string> RequiredConnectionStrings { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using JdeScoping.Core.Interfaces;
|
||||
using JdeScoping.DataAccess.Exceptions;
|
||||
using JdeScoping.DataAccess.Interfaces;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Oracle.ManagedDataAccess.Client;
|
||||
|
||||
@@ -9,35 +9,29 @@ namespace JdeScoping.DataAccess;
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating database connections to all data sources.
|
||||
/// Retrieves connection strings from SecureStore.
|
||||
/// </summary>
|
||||
public class DbConnectionFactory : IDbConnectionFactory
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ISecureStoreService _secureStore;
|
||||
private readonly ILogger<DbConnectionFactory> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DbConnectionFactory"/> class.
|
||||
/// </summary>
|
||||
/// <param name="configuration">Application configuration.</param>
|
||||
/// <param name="secureStore">SecureStore service for retrieving connection strings.</param>
|
||||
/// <param name="logger">Logger instance.</param>
|
||||
public DbConnectionFactory(IConfiguration configuration, ILogger<DbConnectionFactory> logger)
|
||||
public DbConnectionFactory(ISecureStoreService secureStore, ILogger<DbConnectionFactory> logger)
|
||||
{
|
||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
_secureStore = secureStore ?? throw new ArgumentNullException(nameof(secureStore));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SqlConnection> CreateLotFinderConnectionAsync(CancellationToken ct = default)
|
||||
{
|
||||
const string dataSource = "LotFinderDB";
|
||||
var connectionString = _configuration.GetConnectionString(dataSource);
|
||||
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
throw new ConnectionException(
|
||||
$"{dataSource}: Connection string not found in configuration.",
|
||||
dataSource);
|
||||
}
|
||||
const string dataSource = "LotFinder";
|
||||
var connectionString = GetConnectionString(dataSource);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -93,17 +87,24 @@ public class DbConnectionFactory : IDbConnectionFactory
|
||||
return await CreateOracleConnectionAsync("GIW", ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<OracleConnection> CreateOracleConnectionAsync(string dataSource, CancellationToken ct)
|
||||
private string GetConnectionString(string dataSource)
|
||||
{
|
||||
var connectionString = _configuration.GetConnectionString(dataSource);
|
||||
var connectionString = _secureStore.Get(dataSource);
|
||||
|
||||
if (string.IsNullOrEmpty(connectionString))
|
||||
{
|
||||
throw new ConnectionException(
|
||||
$"{dataSource}: Connection string not found in configuration.",
|
||||
$"{dataSource}: Connection string not found in SecureStore. Use ConfigManager to configure the connection string.",
|
||||
dataSource);
|
||||
}
|
||||
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
private async Task<OracleConnection> CreateOracleConnectionAsync(string dataSource, CancellationToken ct)
|
||||
{
|
||||
var connectionString = GetConnectionString(dataSource);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Creating connection to {DataSource}", dataSource);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"LotFinder": "Server=localhost,1434;Database=ScopingTool;User Id=scopingapp;Password=Sc0ping@pp_Dev#2024;TrustServerCertificate=true",
|
||||
"JDE": "Data Source=jde-server:1521/JDEPROD;User Id=${JDE_USER};Password=${JDE_PASSWORD}",
|
||||
"CMS": "Data Source=cms-server:1521/CMSPROD;User Id=${CMS_USER};Password=${CMS_PASSWORD}",
|
||||
"GIW": "Data Source=giw-server:1521/GIWPROD;User Id=${GIW_USER};Password=${GIW_PASSWORD}"
|
||||
"LotFinder": "",
|
||||
"JDE": "",
|
||||
"CMS": "",
|
||||
"GIW": ""
|
||||
},
|
||||
"DataAccess": {
|
||||
"CommandTimeoutSeconds": 120,
|
||||
@@ -54,13 +54,7 @@
|
||||
"RequiredKeys": [
|
||||
"RsaPrivateKey",
|
||||
"ExcelExport:CriteriaSheetPassword",
|
||||
"ExcelExport:DataSheetPassword",
|
||||
"JdeUser",
|
||||
"JdePassword",
|
||||
"GiwUser",
|
||||
"GiwPassword",
|
||||
"CmsUser",
|
||||
"CmsPassword"
|
||||
"ExcelExport:DataSheetPassword"
|
||||
]
|
||||
},
|
||||
"WorkProcessor": {
|
||||
|
||||
@@ -43,8 +43,14 @@ public static class InfrastructureDependencyInjection
|
||||
}
|
||||
|
||||
// Register SecureStore for encrypted secrets storage
|
||||
services.Configure<SecureStoreOptions>(
|
||||
configuration.GetSection(SecureStoreOptions.SectionName));
|
||||
services.Configure<SecureStoreOptions>(opts =>
|
||||
{
|
||||
configuration.GetSection(SecureStoreOptions.SectionName).Bind(opts);
|
||||
|
||||
// Populate RequiredConnectionStrings from ConnectionStrings section
|
||||
var connectionStrings = configuration.GetSection("ConnectionStrings").GetChildren();
|
||||
opts.RequiredConnectionStrings = connectionStrings.Select(c => c.Key).ToList();
|
||||
});
|
||||
|
||||
services.AddSingleton<ISecureStoreService, SecureStoreService>();
|
||||
|
||||
|
||||
@@ -44,6 +44,9 @@ public class SecureStoreService : ISecureStoreService
|
||||
_secretsManager = SecretsManager.LoadStore(_storePath);
|
||||
_secretsManager.LoadKeyFromFile(keyFilePath);
|
||||
LoadKeys();
|
||||
|
||||
// Ensure all required entries exist
|
||||
EnsureRequiredEntries(opts);
|
||||
}
|
||||
else if (opts.AutoCreateStore)
|
||||
{
|
||||
@@ -59,6 +62,9 @@ public class SecureStoreService : ISecureStoreService
|
||||
|
||||
// Save empty store
|
||||
_secretsManager.SaveStore(_storePath);
|
||||
|
||||
// Ensure all required entries exist
|
||||
EnsureRequiredEntries(opts);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -218,4 +224,31 @@ public class SecureStoreService : ISecureStoreService
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures all required entries exist in the store, creating empty values for missing keys.
|
||||
/// </summary>
|
||||
private void EnsureRequiredEntries(SecureStoreOptions opts)
|
||||
{
|
||||
var allRequired = opts.RequiredKeys
|
||||
.Concat(opts.RequiredConnectionStrings)
|
||||
.Distinct();
|
||||
|
||||
var addedAny = false;
|
||||
foreach (var key in allRequired)
|
||||
{
|
||||
if (!Contains(key))
|
||||
{
|
||||
_logger.LogInformation("Creating missing required SecureStore entry: {Key}", key);
|
||||
Set(key, string.Empty);
|
||||
addedAny = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (addedAny)
|
||||
{
|
||||
SaveKeysMetadata();
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using JdeScoping.Core.Interfaces;
|
||||
using JdeScoping.Core.Validation;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@@ -7,9 +6,9 @@ using Microsoft.Extensions.Logging;
|
||||
namespace JdeScoping.Infrastructure.Validation;
|
||||
|
||||
/// <summary>
|
||||
/// Validates connection strings by resolving SecureStore placeholders and verifying format.
|
||||
/// Validates that all connection strings defined in configuration exist in SecureStore.
|
||||
/// </summary>
|
||||
public partial class ConnectionStringValidator : IConfigurationValidator
|
||||
public class ConnectionStringValidator : IConfigurationValidator
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ISecureStoreService _secureStore;
|
||||
@@ -21,9 +20,6 @@ public partial class ConnectionStringValidator : IConfigurationValidator
|
||||
/// <inheritdoc />
|
||||
public string Name => "ConnectionStrings";
|
||||
|
||||
[GeneratedRegex(@"\$\{([^}]+)\}")]
|
||||
private static partial Regex PlaceholderPattern();
|
||||
|
||||
public ConnectionStringValidator(
|
||||
IConfiguration configuration,
|
||||
ISecureStoreService secureStore,
|
||||
@@ -48,96 +44,43 @@ public partial class ConnectionStringValidator : IConfigurationValidator
|
||||
return result;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Validating {Count} connection strings", connectionStrings.Count);
|
||||
_logger.LogDebug("Validating {Count} connection strings from SecureStore", connectionStrings.Count);
|
||||
|
||||
foreach (var connectionString in connectionStrings)
|
||||
{
|
||||
ValidateConnectionString(connectionString.Key, connectionString.Value, result);
|
||||
ValidateConnectionString(connectionString.Key, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ValidateConnectionString(string name, string? connectionString, ConfigurationValidationResult result)
|
||||
private void ValidateConnectionString(string name, ConfigurationValidationResult result)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
_logger.LogDebug("Validating connection string: {Name}", name);
|
||||
|
||||
// Check if connection string exists in SecureStore
|
||||
if (!_secureStore.Contains(name))
|
||||
{
|
||||
result.AddError($"Connection string '{name}' is empty");
|
||||
result.AddError($"Connection string '{name}' not found in SecureStore");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Validating connection string: {Name}", name);
|
||||
// Get the value from SecureStore
|
||||
var connectionStringValue = _secureStore.Get(name);
|
||||
|
||||
// Extract and validate all placeholders
|
||||
var placeholders = ExtractPlaceholders(connectionString);
|
||||
var resolvedConnectionString = connectionString;
|
||||
var hasPlaceholderErrors = false;
|
||||
|
||||
foreach (var placeholder in placeholders)
|
||||
if (string.IsNullOrWhiteSpace(connectionStringValue))
|
||||
{
|
||||
var resolvedValue = ResolvePlaceholder(placeholder, name, result);
|
||||
if (resolvedValue is null)
|
||||
{
|
||||
hasPlaceholderErrors = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Replace placeholder with resolved value for format validation
|
||||
resolvedConnectionString = resolvedConnectionString.Replace($"${{{placeholder}}}", resolvedValue);
|
||||
}
|
||||
result.AddError($"Connection string '{name}' is empty in SecureStore. Use ConfigManager to set the connection string value.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Only validate format if all placeholders were resolved
|
||||
if (!hasPlaceholderErrors)
|
||||
{
|
||||
ValidateConnectionStringFormat(name, resolvedConnectionString, result);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<string> ExtractPlaceholders(string connectionString)
|
||||
{
|
||||
var placeholders = new List<string>();
|
||||
var matches = PlaceholderPattern().Matches(connectionString);
|
||||
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
placeholders.Add(match.Groups[1].Value);
|
||||
}
|
||||
|
||||
return placeholders;
|
||||
}
|
||||
|
||||
private string? ResolvePlaceholder(string key, string connectionStringName, ConfigurationValidationResult result)
|
||||
{
|
||||
if (!_secureStore.Contains(key))
|
||||
{
|
||||
result.AddError($"Connection string '{connectionStringName}' references placeholder '${{{key}}}' but key '{key}' not found in SecureStore");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var value = _secureStore.Get(key);
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
result.AddError($"Connection string '{connectionStringName}' references placeholder '${{{key}}}' but key '{key}' has an empty value in SecureStore");
|
||||
return null;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Resolved placeholder '{Key}' for connection string '{Name}'", key, connectionStringName);
|
||||
return value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.AddError($"Connection string '{connectionStringName}' references placeholder '${{{key}}}' but failed to retrieve from SecureStore: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
// Validate format
|
||||
ValidateConnectionStringFormat(name, connectionStringValue, result);
|
||||
}
|
||||
|
||||
private void ValidateConnectionStringFormat(string name, string connectionString, ConfigurationValidationResult result)
|
||||
{
|
||||
// Check for required connection string components
|
||||
// Support both "Server=" (SQL Server) and "Data Source=" (Oracle/generic)
|
||||
var hasServer = connectionString.Contains("Server=", StringComparison.OrdinalIgnoreCase) ||
|
||||
connectionString.Contains("Data Source=", StringComparison.OrdinalIgnoreCase) ||
|
||||
connectionString.Contains("Host=", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
@@ -71,11 +71,15 @@ public interface ISecureStoreManager
|
||||
void RemoveSecret(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Ensures all required keys exist in the store, creating blank values for any missing keys.
|
||||
/// Ensures all required entries exist in the store, creating blank values for any missing keys.
|
||||
/// Handles both general required keys and connection string names.
|
||||
/// </summary>
|
||||
/// <param name="requiredKeys">List of keys that must exist.</param>
|
||||
/// <param name="requiredKeys">List of general secret keys that must exist.</param>
|
||||
/// <param name="connectionStringNames">List of connection string names that must exist.</param>
|
||||
/// <returns>List of keys that were added.</returns>
|
||||
IReadOnlyList<string> EnsureRequiredKeys(IEnumerable<string> requiredKeys);
|
||||
IReadOnlyList<string> EnsureAllRequiredEntries(
|
||||
IEnumerable<string> requiredKeys,
|
||||
IEnumerable<string> connectionStringNames);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a new key file for use with store encryption.
|
||||
|
||||
@@ -193,7 +193,9 @@ public class SecureStoreManager : ISecureStoreManager, IDisposable
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<string> EnsureRequiredKeys(IEnumerable<string> requiredKeys)
|
||||
public IReadOnlyList<string> EnsureAllRequiredEntries(
|
||||
IEnumerable<string> requiredKeys,
|
||||
IEnumerable<string> connectionStringNames)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
@@ -201,11 +203,15 @@ public class SecureStoreManager : ISecureStoreManager, IDisposable
|
||||
throw new InvalidOperationException("No store is currently open.");
|
||||
|
||||
var addedKeys = new List<string>();
|
||||
foreach (var key in requiredKeys)
|
||||
var allRequired = requiredKeys
|
||||
.Concat(connectionStringNames)
|
||||
.Distinct();
|
||||
|
||||
foreach (var key in allRequired)
|
||||
{
|
||||
if (!_keys.Contains(key))
|
||||
{
|
||||
_logger.LogInformation("Adding missing required key: {Key}", key);
|
||||
_logger.LogInformation("Adding missing required entry: {Key}", key);
|
||||
SetSecret(key, string.Empty);
|
||||
addedKeys.Add(key);
|
||||
}
|
||||
|
||||
+71
-8
@@ -2,16 +2,18 @@ using System.Collections.ObjectModel;
|
||||
using System.Windows.Input;
|
||||
using JdeScoping.ConfigManager.Models;
|
||||
using JdeScoping.ConfigManager.Services;
|
||||
using JdeScoping.ConfigManager.Services.SecureStore;
|
||||
|
||||
namespace JdeScoping.ConfigManager.ViewModels.Forms;
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel for editing the ConnectionStrings configuration section.
|
||||
/// Manages a collection of connection string entries with add, delete, validate, and test commands.
|
||||
/// ViewModel for editing connection strings stored in SecureStore.
|
||||
/// Connection string names come from configuration, values come from SecureStore.
|
||||
/// </summary>
|
||||
public class ConnectionStringsFormViewModel : ViewModelBase
|
||||
{
|
||||
private readonly ConnectionStringsSection _model;
|
||||
private readonly ISecureStoreManager _secureStoreManager;
|
||||
private readonly Action _onChanged;
|
||||
private readonly IDialogService _dialogService;
|
||||
private readonly IConnectionTestService _connectionTestService;
|
||||
@@ -21,27 +23,42 @@ public class ConnectionStringsFormViewModel : ViewModelBase
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ConnectionStringsFormViewModel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="model">The connection strings section model.</param>
|
||||
/// <param name="model">The connection strings section model (provides names).</param>
|
||||
/// <param name="secureStoreManager">The SecureStore manager for reading/writing values.</param>
|
||||
/// <param name="onChanged">The action to invoke when any property changes.</param>
|
||||
/// <param name="dialogService">The dialog service for showing messages and confirmations.</param>
|
||||
/// <param name="connectionTestService">The service for testing database connections.</param>
|
||||
public ConnectionStringsFormViewModel(
|
||||
ConnectionStringsSection model,
|
||||
ISecureStoreManager secureStoreManager,
|
||||
Action onChanged,
|
||||
IDialogService dialogService,
|
||||
IConnectionTestService connectionTestService)
|
||||
{
|
||||
_model = model ?? throw new ArgumentNullException(nameof(model));
|
||||
_secureStoreManager = secureStoreManager ?? throw new ArgumentNullException(nameof(secureStoreManager));
|
||||
_onChanged = onChanged ?? throw new ArgumentNullException(nameof(onChanged));
|
||||
_dialogService = dialogService ?? throw new ArgumentNullException(nameof(dialogService));
|
||||
_connectionTestService = connectionTestService ?? throw new ArgumentNullException(nameof(connectionTestService));
|
||||
|
||||
Connections = new ObservableCollection<ConnectionStringEntryViewModel>();
|
||||
|
||||
// Initialize view models from model entries
|
||||
// Initialize view models from model entries, loading values from SecureStore
|
||||
foreach (var entry in _model.Entries)
|
||||
{
|
||||
Connections.Add(new ConnectionStringEntryViewModel(entry, _onChanged));
|
||||
// Load the actual connection string value from SecureStore
|
||||
var secureStoreValue = _secureStoreManager.IsStoreOpen && !string.IsNullOrEmpty(entry.Name)
|
||||
? TryGetSecret(entry.Name)
|
||||
: null;
|
||||
|
||||
// Update entry's RawConnectionString with SecureStore value if available
|
||||
if (!string.IsNullOrEmpty(secureStoreValue))
|
||||
{
|
||||
entry.RawConnectionString = secureStoreValue;
|
||||
entry.Provider = ConnectionProvider.Generic; // Use Generic since we have the full string
|
||||
}
|
||||
|
||||
Connections.Add(new ConnectionStringEntryViewModel(entry, OnEntryChanged));
|
||||
}
|
||||
|
||||
// Initialize commands
|
||||
@@ -51,6 +68,32 @@ public class ConnectionStringsFormViewModel : ViewModelBase
|
||||
TestConnectionCommand = new AsyncRelayCommand(TestConnectionAsync, () => HasSelection && !IsTesting);
|
||||
}
|
||||
|
||||
private string? TryGetSecret(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _secureStoreManager.GetSecret(key);
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEntryChanged()
|
||||
{
|
||||
// When an entry changes, save its value to SecureStore
|
||||
if (SelectedConnection != null && !string.IsNullOrEmpty(SelectedConnection.Name))
|
||||
{
|
||||
var connectionString = SelectedConnection.GeneratedConnectionString;
|
||||
if (_secureStoreManager.IsStoreOpen)
|
||||
{
|
||||
_secureStoreManager.SetSecret(SelectedConnection.Name, connectionString);
|
||||
}
|
||||
}
|
||||
_onChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of connection string entry view models.
|
||||
/// </summary>
|
||||
@@ -136,12 +179,19 @@ public class ConnectionStringsFormViewModel : ViewModelBase
|
||||
{
|
||||
var entry = new ConnectionStringEntry
|
||||
{
|
||||
Name = "NewConnection"
|
||||
Name = "NewConnection",
|
||||
Provider = ConnectionProvider.Generic
|
||||
};
|
||||
|
||||
_model.Entries.Add(entry);
|
||||
|
||||
var viewModel = new ConnectionStringEntryViewModel(entry, _onChanged);
|
||||
// Create empty entry in SecureStore
|
||||
if (_secureStoreManager.IsStoreOpen)
|
||||
{
|
||||
_secureStoreManager.SetSecret(entry.Name, string.Empty);
|
||||
}
|
||||
|
||||
var viewModel = new ConnectionStringEntryViewModel(entry, OnEntryChanged);
|
||||
Connections.Add(viewModel);
|
||||
|
||||
SelectedConnection = viewModel;
|
||||
@@ -160,11 +210,24 @@ public class ConnectionStringsFormViewModel : ViewModelBase
|
||||
var name = SelectedConnection.Name;
|
||||
var confirmed = await _dialogService.ShowConfirmationAsync(
|
||||
"Delete Connection",
|
||||
$"Delete connection '{name}'?");
|
||||
$"Delete connection '{name}'? This will also remove the SecureStore entry.");
|
||||
|
||||
if (!confirmed)
|
||||
return;
|
||||
|
||||
// Remove from SecureStore
|
||||
if (_secureStoreManager.IsStoreOpen && !string.IsNullOrEmpty(name))
|
||||
{
|
||||
try
|
||||
{
|
||||
_secureStoreManager.RemoveSecret(name);
|
||||
}
|
||||
catch (KeyNotFoundException)
|
||||
{
|
||||
// Entry didn't exist in SecureStore, that's OK
|
||||
}
|
||||
}
|
||||
|
||||
// Find the model entry to remove
|
||||
var modelEntry = _model.Entries.FirstOrDefault(e => e.Name == name);
|
||||
if (modelEntry != null)
|
||||
|
||||
@@ -346,14 +346,18 @@ public class MainWindowViewModel : ViewModelBase
|
||||
_secureStoreManager.OpenStore(storePath, keyFilePath);
|
||||
}
|
||||
|
||||
// Ensure all required keys exist
|
||||
if (secureStoreConfig.RequiredKeys?.Count > 0)
|
||||
// Ensure all required entries exist (both RequiredKeys and connection strings)
|
||||
var connectionStringNames = _appSettings?.ConnectionStrings?.Entries
|
||||
.Select(e => e.Name)
|
||||
.Where(n => !string.IsNullOrEmpty(n))
|
||||
?? Enumerable.Empty<string>();
|
||||
|
||||
var requiredKeys = secureStoreConfig.RequiredKeys ?? new List<string>();
|
||||
|
||||
var addedKeys = _secureStoreManager.EnsureAllRequiredEntries(requiredKeys, connectionStringNames);
|
||||
if (addedKeys.Count > 0)
|
||||
{
|
||||
var addedKeys = _secureStoreManager.EnsureRequiredKeys(secureStoreConfig.RequiredKeys);
|
||||
if (addedKeys.Count > 0)
|
||||
{
|
||||
_logger?.LogInformation("Added {Count} missing required keys", addedKeys.Count);
|
||||
}
|
||||
_logger?.LogInformation("Added {Count} missing required SecureStore entries", addedKeys.Count);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -575,6 +579,7 @@ public class MainWindowViewModel : ViewModelBase
|
||||
"ExcelExport" => new ExcelExportFormViewModel(_appSettings.ExcelExport, MarkAsChanged),
|
||||
"ConnectionStrings" when _dialogService != null => new ConnectionStringsFormViewModel(
|
||||
_appSettings.ConnectionStrings,
|
||||
_secureStoreManager,
|
||||
MarkAsChanged,
|
||||
_dialogService,
|
||||
_connectionTestService),
|
||||
|
||||
Reference in New Issue
Block a user