refactor(securestore): remove password-based authentication in favor of key-file only
Simplify SecureStore by removing MasterKeyEnvVar and password-based methods, leaving only key-file authentication for better security practices.
This commit is contained in:
@@ -17,17 +17,11 @@ public class SecureStoreOptions
|
||||
public string StorePath { get; set; } = "data/secrets.json";
|
||||
|
||||
/// <summary>
|
||||
/// Path to the key file (used in development).
|
||||
/// Path to the key file used for encryption/decryption.
|
||||
/// Defaults to "data/secrets.key" relative to app directory.
|
||||
/// </summary>
|
||||
public string KeyFilePath { get; set; } = "data/secrets.key";
|
||||
|
||||
/// <summary>
|
||||
/// Environment variable name containing the master key (used in production).
|
||||
/// If set and the env var exists, it takes precedence over the key file.
|
||||
/// </summary>
|
||||
public string MasterKeyEnvVar { get; set; } = "SCOPINGTOOL_MASTER_KEY";
|
||||
|
||||
/// <summary>
|
||||
/// Whether to auto-create the store and generate a key file on first run.
|
||||
/// </summary>
|
||||
|
||||
@@ -19,99 +19,7 @@
|
||||
"BulkCopyBatchSize": 10000,
|
||||
"LookbackMultiplier": 3,
|
||||
"PurgeRetentionDays": 30,
|
||||
"SyncTimeoutSeconds": 3600,
|
||||
"DataSources": [
|
||||
{
|
||||
"TableName": "WorkOrder_Curr",
|
||||
"SourceSystem": "JDE",
|
||||
"SourceData": "WORKORDER",
|
||||
"IsEnabled": true,
|
||||
"MassConfig": { "Enabled": true, "IntervalMinutes": 10080 },
|
||||
"DailyConfig": { "Enabled": true, "IntervalMinutes": 1440 },
|
||||
"HourlyConfig": { "Enabled": true, "IntervalMinutes": 60 }
|
||||
},
|
||||
{
|
||||
"TableName": "LotUsage_Curr",
|
||||
"SourceSystem": "JDE",
|
||||
"SourceData": "LOTUSAGE",
|
||||
"IsEnabled": true,
|
||||
"MassConfig": { "Enabled": true, "IntervalMinutes": 10080 },
|
||||
"DailyConfig": { "Enabled": true, "IntervalMinutes": 1440 },
|
||||
"HourlyConfig": { "Enabled": true, "IntervalMinutes": 60 }
|
||||
},
|
||||
{
|
||||
"TableName": "Item",
|
||||
"SourceSystem": "JDE",
|
||||
"SourceData": "ITEM",
|
||||
"IsEnabled": true,
|
||||
"MassConfig": { "Enabled": true, "IntervalMinutes": 10080 },
|
||||
"DailyConfig": { "Enabled": true, "IntervalMinutes": 1440 },
|
||||
"HourlyConfig": { "Enabled": false }
|
||||
},
|
||||
{
|
||||
"TableName": "Lot",
|
||||
"SourceSystem": "JDE",
|
||||
"SourceData": "LOT",
|
||||
"IsEnabled": true,
|
||||
"MassConfig": { "Enabled": true, "IntervalMinutes": 10080 },
|
||||
"DailyConfig": { "Enabled": true, "IntervalMinutes": 1440 },
|
||||
"HourlyConfig": { "Enabled": true, "IntervalMinutes": 60 }
|
||||
},
|
||||
{
|
||||
"TableName": "WorkCenter",
|
||||
"SourceSystem": "JDE",
|
||||
"SourceData": "WORKCENTER",
|
||||
"IsEnabled": true,
|
||||
"MassConfig": { "Enabled": true, "IntervalMinutes": 10080 },
|
||||
"DailyConfig": { "Enabled": true, "IntervalMinutes": 1440 },
|
||||
"HourlyConfig": { "Enabled": false }
|
||||
},
|
||||
{
|
||||
"TableName": "ProfitCenter",
|
||||
"SourceSystem": "JDE",
|
||||
"SourceData": "PROFITCENTER",
|
||||
"IsEnabled": true,
|
||||
"MassConfig": { "Enabled": true, "IntervalMinutes": 10080 },
|
||||
"DailyConfig": { "Enabled": true, "IntervalMinutes": 1440 },
|
||||
"HourlyConfig": { "Enabled": false }
|
||||
},
|
||||
{
|
||||
"TableName": "JdeUser",
|
||||
"SourceSystem": "JDE",
|
||||
"SourceData": "JDEUSER",
|
||||
"IsEnabled": true,
|
||||
"MassConfig": { "Enabled": true, "IntervalMinutes": 10080 },
|
||||
"DailyConfig": { "Enabled": true, "IntervalMinutes": 1440 },
|
||||
"HourlyConfig": { "Enabled": false }
|
||||
},
|
||||
{
|
||||
"TableName": "Branch",
|
||||
"SourceSystem": "JDE",
|
||||
"SourceData": "BRANCH",
|
||||
"IsEnabled": true,
|
||||
"MassConfig": { "Enabled": true, "IntervalMinutes": 10080 },
|
||||
"DailyConfig": { "Enabled": true, "IntervalMinutes": 1440 },
|
||||
"HourlyConfig": { "Enabled": false }
|
||||
},
|
||||
{
|
||||
"TableName": "MisData_Curr",
|
||||
"SourceSystem": "CMS",
|
||||
"SourceData": "MISDATA_CURR",
|
||||
"IsEnabled": true,
|
||||
"MassConfig": { "Enabled": true, "IntervalMinutes": 10080 },
|
||||
"DailyConfig": { "Enabled": true, "IntervalMinutes": 1440 },
|
||||
"HourlyConfig": { "Enabled": false }
|
||||
},
|
||||
{
|
||||
"TableName": "MisData_Hist",
|
||||
"SourceSystem": "CMS",
|
||||
"SourceData": "MISDATA_HIST",
|
||||
"IsEnabled": true,
|
||||
"MassConfig": { "Enabled": true, "IntervalMinutes": 10080 },
|
||||
"DailyConfig": { "Enabled": false },
|
||||
"HourlyConfig": { "Enabled": false }
|
||||
}
|
||||
]
|
||||
"SyncTimeoutSeconds": 3600
|
||||
},
|
||||
"Auth": {
|
||||
"CookieName": "ScopingTool.Auth",
|
||||
@@ -142,7 +50,6 @@
|
||||
"SecureStore": {
|
||||
"StorePath": "data/secrets.json",
|
||||
"KeyFilePath": "data/secrets.key",
|
||||
"MasterKeyEnvVar": "SCOPINGTOOL_MASTER_KEY",
|
||||
"AutoCreateStore": true,
|
||||
"RequiredKeys": [
|
||||
"RsaPrivateKey",
|
||||
|
||||
@@ -37,26 +37,12 @@ public class SecureStoreService : ISecureStoreService
|
||||
? opts.KeyFilePath
|
||||
: Path.Combine(AppContext.BaseDirectory, opts.KeyFilePath);
|
||||
|
||||
// Check for master key in environment variable (production)
|
||||
var masterKey = Environment.GetEnvironmentVariable(opts.MasterKeyEnvVar);
|
||||
var useMasterKey = !string.IsNullOrEmpty(masterKey);
|
||||
|
||||
if (File.Exists(_storePath))
|
||||
{
|
||||
// Load existing store
|
||||
_logger.LogInformation("Loading secure store from {StorePath}", _storePath);
|
||||
_secretsManager = SecretsManager.LoadStore(_storePath);
|
||||
|
||||
// Load the key
|
||||
if (useMasterKey)
|
||||
{
|
||||
_secretsManager.LoadKeyFromPassword(masterKey!);
|
||||
}
|
||||
else
|
||||
{
|
||||
_secretsManager.LoadKeyFromFile(keyFilePath);
|
||||
}
|
||||
|
||||
_secretsManager.LoadKeyFromFile(keyFilePath);
|
||||
LoadKeys();
|
||||
}
|
||||
else if (opts.AutoCreateStore)
|
||||
@@ -64,21 +50,12 @@ public class SecureStoreService : ISecureStoreService
|
||||
// Create new store
|
||||
_logger.LogInformation("Creating new secure store at {StorePath}", _storePath);
|
||||
EnsureDirectory(_storePath);
|
||||
EnsureDirectory(keyFilePath);
|
||||
|
||||
_secretsManager = SecretsManager.CreateStore();
|
||||
|
||||
if (useMasterKey)
|
||||
{
|
||||
_secretsManager.LoadKeyFromPassword(masterKey!);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Generate key file for development
|
||||
EnsureDirectory(keyFilePath);
|
||||
_secretsManager.GenerateKey();
|
||||
_secretsManager.ExportKey(keyFilePath);
|
||||
_logger.LogInformation("Generated key file at {KeyFilePath}", keyFilePath);
|
||||
}
|
||||
_secretsManager.GenerateKey();
|
||||
_secretsManager.ExportKey(keyFilePath);
|
||||
_logger.LogInformation("Generated key file at {KeyFilePath}", keyFilePath);
|
||||
|
||||
// Save empty store
|
||||
_secretsManager.SaveStore(_storePath);
|
||||
|
||||
@@ -252,15 +252,10 @@ public class SecureStoreSection
|
||||
public string StorePath { get; set; } = "data/secrets.json";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path to the key file for decryption.
|
||||
/// Gets or sets the path to the key file for encryption/decryption.
|
||||
/// </summary>
|
||||
public string KeyFilePath { get; set; } = "data/secrets.key";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the environment variable name for the master key.
|
||||
/// </summary>
|
||||
public string MasterKeyEnvVar { get; set; } = "SCOPINGTOOL_MASTER_KEY";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to auto-create the store if it doesn't exist.
|
||||
/// </summary>
|
||||
|
||||
+2
-88
@@ -10,10 +10,6 @@ public class NewStoreDialogViewModel : ViewModelBase
|
||||
{
|
||||
private string _storePath = string.Empty;
|
||||
private string _keyFilePath = string.Empty;
|
||||
private string _password = string.Empty;
|
||||
private string _confirmPassword = string.Empty;
|
||||
private bool _useKeyFile = true;
|
||||
private bool _usePassword;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewStoreDialogViewModel"/> class.
|
||||
@@ -51,64 +47,6 @@ public class NewStoreDialogViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password for store encryption.
|
||||
/// </summary>
|
||||
public string Password
|
||||
{
|
||||
get => _password;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _password, value))
|
||||
NotifyValidationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password confirmation value.
|
||||
/// </summary>
|
||||
public string ConfirmPassword
|
||||
{
|
||||
get => _confirmPassword;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _confirmPassword, value))
|
||||
NotifyValidationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use key file for store encryption.
|
||||
/// </summary>
|
||||
public bool UseKeyFile
|
||||
{
|
||||
get => _useKeyFile;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _useKeyFile, value))
|
||||
{
|
||||
if (value) UsePassword = false;
|
||||
NotifyValidationChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use password for store encryption.
|
||||
/// </summary>
|
||||
public bool UsePassword
|
||||
{
|
||||
get => _usePassword;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _usePassword, value))
|
||||
{
|
||||
if (value) UseKeyFile = false;
|
||||
NotifyValidationChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command to browse for store path location.
|
||||
/// </summary>
|
||||
@@ -127,22 +65,7 @@ public class NewStoreDialogViewModel : ViewModelBase
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the dialog input is valid.
|
||||
/// </summary>
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(StorePath))
|
||||
return false;
|
||||
|
||||
if (UseKeyFile)
|
||||
return !string.IsNullOrWhiteSpace(KeyFilePath);
|
||||
|
||||
if (UsePassword)
|
||||
return !string.IsNullOrWhiteSpace(Password) && Password == ConfirmPassword;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool IsValid => !string.IsNullOrWhiteSpace(StorePath) && !string.IsNullOrWhiteSpace(KeyFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the validation error message, or null if valid.
|
||||
@@ -154,18 +77,9 @@ public class NewStoreDialogViewModel : ViewModelBase
|
||||
if (string.IsNullOrWhiteSpace(StorePath))
|
||||
return SecureStoreStrings.StorePathRequired;
|
||||
|
||||
if (UseKeyFile && string.IsNullOrWhiteSpace(KeyFilePath))
|
||||
if (string.IsNullOrWhiteSpace(KeyFilePath))
|
||||
return SecureStoreStrings.KeyFilePathRequired;
|
||||
|
||||
if (UsePassword)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Password))
|
||||
return SecureStoreStrings.PasswordRequired;
|
||||
|
||||
if (Password != ConfirmPassword)
|
||||
return SecureStoreStrings.PasswordsDoNotMatch;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-70
@@ -10,9 +10,6 @@ public class UnlockStoreDialogViewModel : ViewModelBase
|
||||
{
|
||||
private readonly string _storePath;
|
||||
private string _keyFilePath = string.Empty;
|
||||
private string _password = string.Empty;
|
||||
private bool _useKeyFile = true;
|
||||
private bool _usePassword;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UnlockStoreDialogViewModel"/> class.
|
||||
@@ -42,51 +39,6 @@ public class UnlockStoreDialogViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password for store decryption.
|
||||
/// </summary>
|
||||
public string Password
|
||||
{
|
||||
get => _password;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _password, value))
|
||||
NotifyValidationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use key file for store decryption.
|
||||
/// </summary>
|
||||
public bool UseKeyFile
|
||||
{
|
||||
get => _useKeyFile;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _useKeyFile, value))
|
||||
{
|
||||
if (value) UsePassword = false;
|
||||
NotifyValidationChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use password for store decryption.
|
||||
/// </summary>
|
||||
public bool UsePassword
|
||||
{
|
||||
get => _usePassword;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _usePassword, value))
|
||||
{
|
||||
if (value) UseKeyFile = false;
|
||||
NotifyValidationChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command to browse for key file path location.
|
||||
/// </summary>
|
||||
@@ -99,19 +51,10 @@ public class UnlockStoreDialogViewModel : ViewModelBase
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UseKeyFile)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(KeyFilePath))
|
||||
return false;
|
||||
if (string.IsNullOrWhiteSpace(KeyFilePath))
|
||||
return false;
|
||||
|
||||
if (!System.IO.File.Exists(KeyFilePath))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (UsePassword)
|
||||
return !string.IsNullOrWhiteSpace(Password);
|
||||
|
||||
return false;
|
||||
return System.IO.File.Exists(KeyFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,17 +65,11 @@ public class UnlockStoreDialogViewModel : ViewModelBase
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UseKeyFile)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(KeyFilePath))
|
||||
return SecureStoreStrings.KeyFilePathRequired;
|
||||
if (string.IsNullOrWhiteSpace(KeyFilePath))
|
||||
return SecureStoreStrings.KeyFilePathRequired;
|
||||
|
||||
if (!System.IO.File.Exists(KeyFilePath))
|
||||
return SecureStoreStrings.KeyFileNotFound;
|
||||
}
|
||||
|
||||
if (UsePassword && string.IsNullOrWhiteSpace(Password))
|
||||
return SecureStoreStrings.PasswordRequired;
|
||||
if (!System.IO.File.Exists(KeyFilePath))
|
||||
return SecureStoreStrings.KeyFileNotFound;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
x:Class="JdeScoping.ConfigManager.Views.Dialogs.NewStoreDialog"
|
||||
x:DataType="vm:NewStoreDialogViewModel"
|
||||
Title="Create New Secure Store"
|
||||
Width="550" Height="480"
|
||||
MinWidth="450" MinHeight="400"
|
||||
Width="550" Height="350"
|
||||
MinWidth="450" MinHeight="300"
|
||||
Background="#151920"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
CanResize="False"
|
||||
@@ -56,28 +56,14 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Encryption Method -->
|
||||
<!-- Key File -->
|
||||
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Encryption Method" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
<RadioButton Content="Use Key File (recommended for production)"
|
||||
IsChecked="{Binding UseKeyFile, Mode=TwoWay}"
|
||||
Foreground="#9BA8B8" Margin="0,4"/>
|
||||
<RadioButton Content="Use Password"
|
||||
IsChecked="{Binding UsePassword, Mode=TwoWay}"
|
||||
Foreground="#9BA8B8" Margin="0,4"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Key File Settings -->
|
||||
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16"
|
||||
IsVisible="{Binding UseKeyFile}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Key File" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
<TextBlock Text="The key file is required to encrypt and decrypt the store."
|
||||
Foreground="#9BA8B8" FontSize="12" TextWrapping="Wrap"/>
|
||||
<Grid ColumnDefinitions="*,Auto,Auto" Margin="0,4,0,0">
|
||||
<TextBox Grid.Column="0"
|
||||
Text="{Binding KeyFilePath, Mode=TwoWay}"
|
||||
@@ -96,32 +82,6 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Password Settings -->
|
||||
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16"
|
||||
IsVisible="{Binding UsePassword}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Password" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
<Grid ColumnDefinitions="120,*" RowDefinitions="Auto,Auto" Margin="0,4,0,0">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Password:"
|
||||
Foreground="#9BA8B8" VerticalAlignment="Center" Margin="0,8"/>
|
||||
<TextBox Grid.Row="0" Grid.Column="1"
|
||||
PasswordChar="*"
|
||||
Text="{Binding Password, Mode=TwoWay}"
|
||||
Background="#0D0F12" Foreground="#E6EDF5"
|
||||
BorderBrush="#3D4550" Padding="8" Margin="0,4"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Confirm:"
|
||||
Foreground="#9BA8B8" VerticalAlignment="Center" Margin="0,8"/>
|
||||
<TextBox Grid.Row="1" Grid.Column="1"
|
||||
PasswordChar="*"
|
||||
Text="{Binding ConfirmPassword, Mode=TwoWay}"
|
||||
Background="#0D0F12" Foreground="#E6EDF5"
|
||||
BorderBrush="#3D4550" Padding="8" Margin="0,4"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Validation Error -->
|
||||
<TextBlock Text="{Binding ValidationError}"
|
||||
Foreground="#FF6B6B" FontSize="12"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
x:Class="JdeScoping.ConfigManager.Views.Dialogs.UnlockStoreDialog"
|
||||
x:DataType="vm:UnlockStoreDialogViewModel"
|
||||
Title="Unlock Secure Store"
|
||||
Width="500" Height="400"
|
||||
MinWidth="400" MinHeight="350"
|
||||
Width="500" Height="320"
|
||||
MinWidth="400" MinHeight="280"
|
||||
Background="#151920"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
CanResize="False"
|
||||
@@ -49,28 +49,14 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Decryption Method -->
|
||||
<!-- Key File -->
|
||||
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Decryption Method" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
<RadioButton Content="Use Key File"
|
||||
IsChecked="{Binding UseKeyFile, Mode=TwoWay}"
|
||||
Foreground="#9BA8B8" Margin="0,4"/>
|
||||
<RadioButton Content="Use Password"
|
||||
IsChecked="{Binding UsePassword, Mode=TwoWay}"
|
||||
Foreground="#9BA8B8" Margin="0,4"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Key File Settings -->
|
||||
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16"
|
||||
IsVisible="{Binding UseKeyFile}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Key File" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
<TextBlock Text="Select the key file used to encrypt this store."
|
||||
Foreground="#9BA8B8" FontSize="12" TextWrapping="Wrap"/>
|
||||
<Grid ColumnDefinitions="*,Auto" Margin="0,4,0,0">
|
||||
<TextBox Grid.Column="0"
|
||||
Text="{Binding KeyFilePath, Mode=TwoWay}"
|
||||
@@ -85,25 +71,6 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Password Settings -->
|
||||
<Border Background="#1C2128" BorderBrush="#2D3540" BorderThickness="1"
|
||||
CornerRadius="4" Padding="16"
|
||||
IsVisible="{Binding UsePassword}">
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="Password" Foreground="#E6EDF5"
|
||||
FontWeight="SemiBold" FontSize="14"/>
|
||||
<Grid ColumnDefinitions="100,*" Margin="0,4,0,0">
|
||||
<TextBlock Text="Password:" Foreground="#9BA8B8"
|
||||
VerticalAlignment="Center"/>
|
||||
<TextBox Grid.Column="1"
|
||||
PasswordChar="*"
|
||||
Text="{Binding Password, Mode=TwoWay}"
|
||||
Background="#0D0F12" Foreground="#E6EDF5"
|
||||
BorderBrush="#3D4550" Padding="8" Margin="0,4"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Validation Error -->
|
||||
<TextBlock Text="{Binding ValidationError}"
|
||||
Foreground="#FF6B6B" FontSize="12"
|
||||
|
||||
@@ -23,55 +23,33 @@ public class StoreUseCases
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new store with either key file or password authentication.
|
||||
/// Creates a new store with key file authentication.
|
||||
/// </summary>
|
||||
/// <param name="storePath">The path where the store will be created.</param>
|
||||
/// <param name="keyFilePath">The path to the key file, or null for password-based authentication.</param>
|
||||
/// <param name="password">The password for authentication, or null for key file-based authentication.</param>
|
||||
public void CreateStore(string storePath, string? keyFilePath, string? password)
|
||||
/// <param name="keyFilePath">The path to the key file.</param>
|
||||
public void CreateStore(string storePath, string keyFilePath)
|
||||
{
|
||||
_logger.LogInformation("Creating store at {StorePath}", storePath);
|
||||
if (string.IsNullOrEmpty(keyFilePath))
|
||||
throw new ArgumentException("Key file path must be provided.", nameof(keyFilePath));
|
||||
|
||||
if (!string.IsNullOrEmpty(keyFilePath))
|
||||
{
|
||||
_storeManager.CreateStore(storePath, keyFilePath);
|
||||
_logger.LogInformation("Store created with key file: {KeyFilePath}", keyFilePath);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
_storeManager.CreateStoreWithPassword(storePath, password);
|
||||
_logger.LogInformation("Password-protected store created");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Either key file path or password must be provided.");
|
||||
}
|
||||
_logger.LogInformation("Creating store at {StorePath}", storePath);
|
||||
_storeManager.CreateStore(storePath, keyFilePath);
|
||||
_logger.LogInformation("Store created with key file: {KeyFilePath}", keyFilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens an existing store with either key file or password authentication.
|
||||
/// Opens an existing store with key file authentication.
|
||||
/// </summary>
|
||||
/// <param name="storePath">The path to the existing store.</param>
|
||||
/// <param name="keyFilePath">The path to the key file, or null for password-based authentication.</param>
|
||||
/// <param name="password">The password for authentication, or null for key file-based authentication.</param>
|
||||
public void OpenStore(string storePath, string? keyFilePath, string? password)
|
||||
/// <param name="keyFilePath">The path to the key file.</param>
|
||||
public void OpenStore(string storePath, string keyFilePath)
|
||||
{
|
||||
_logger.LogInformation("Opening store at {StorePath}", storePath);
|
||||
if (string.IsNullOrEmpty(keyFilePath))
|
||||
throw new ArgumentException("Key file path must be provided.", nameof(keyFilePath));
|
||||
|
||||
if (!string.IsNullOrEmpty(keyFilePath))
|
||||
{
|
||||
_storeManager.OpenStore(storePath, keyFilePath);
|
||||
_logger.LogDebug("Store opened with key file");
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
_storeManager.OpenStoreWithPassword(storePath, password);
|
||||
_logger.LogDebug("Store opened with password");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Either key file path or password must be provided.");
|
||||
}
|
||||
_logger.LogInformation("Opening store at {StorePath}", storePath);
|
||||
_storeManager.OpenStore(storePath, keyFilePath);
|
||||
_logger.LogDebug("Store opened with key file");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -27,13 +27,6 @@ public interface ISecureStoreManager
|
||||
/// <param name="keyFilePath">Path for the key file (.key).</param>
|
||||
void CreateStore(string storePath, string keyFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new store secured with a password.
|
||||
/// </summary>
|
||||
/// <param name="storePath">Path for the new store file (.json).</param>
|
||||
/// <param name="password">Password to encrypt the store.</param>
|
||||
void CreateStoreWithPassword(string storePath, string password);
|
||||
|
||||
/// <summary>
|
||||
/// Opens an existing store using a key file.
|
||||
/// </summary>
|
||||
@@ -41,13 +34,6 @@ public interface ISecureStoreManager
|
||||
/// <param name="keyFilePath">Path to the key file (.key).</param>
|
||||
void OpenStore(string storePath, string keyFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Opens an existing store using a password.
|
||||
/// </summary>
|
||||
/// <param name="storePath">Path to the store file (.json).</param>
|
||||
/// <param name="password">Password to decrypt the store.</param>
|
||||
void OpenStoreWithPassword(string storePath, string password);
|
||||
|
||||
/// <summary>
|
||||
/// Closes the currently open store without saving.
|
||||
/// </summary>
|
||||
|
||||
@@ -72,29 +72,6 @@ public class SecureStoreManager : ISecureStoreManager, IDisposable
|
||||
_logger.LogInformation("Store created with key file: {KeyFilePath}", keyFilePath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CreateStoreWithPassword(string storePath, string password)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_logger.LogInformation("Creating password-protected store at {StorePath}", storePath);
|
||||
CloseStoreInternal();
|
||||
|
||||
if (string.IsNullOrEmpty(password))
|
||||
throw new ArgumentException("Password cannot be empty.", nameof(password));
|
||||
|
||||
EnsureDirectory(storePath);
|
||||
|
||||
_secretsManager = SecretsManager.CreateStore();
|
||||
_secretsManager.LoadKeyFromPassword(password);
|
||||
|
||||
_currentStorePath = storePath;
|
||||
_keys.Clear();
|
||||
_hasUnsavedChanges = true;
|
||||
|
||||
Save();
|
||||
_logger.LogInformation("Password-protected store created");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OpenStore(string storePath, string keyFilePath)
|
||||
{
|
||||
@@ -117,28 +94,6 @@ public class SecureStoreManager : ISecureStoreManager, IDisposable
|
||||
_logger.LogDebug("Store opened with key file, contains {KeyCount} keys", _keys.Count);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void OpenStoreWithPassword(string storePath, string password)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
_logger.LogInformation("Opening store at {StorePath} with password", storePath);
|
||||
CloseStoreInternal();
|
||||
|
||||
if (!File.Exists(storePath))
|
||||
throw new FileNotFoundException("Store file not found.", storePath);
|
||||
|
||||
if (string.IsNullOrEmpty(password))
|
||||
throw new ArgumentException("Password cannot be empty.", nameof(password));
|
||||
|
||||
_secretsManager = SecretsManager.LoadStore(storePath);
|
||||
_secretsManager.LoadKeyFromPassword(password);
|
||||
|
||||
_currentStorePath = storePath;
|
||||
LoadKeysMetadata();
|
||||
_hasUnsavedChanges = false;
|
||||
_logger.LogDebug("Store opened with password, contains {KeyCount} keys", _keys.Count);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CloseStore()
|
||||
{
|
||||
|
||||
@@ -10,10 +10,6 @@ public class NewStoreDialogViewModel : ViewModelBase
|
||||
{
|
||||
private string _storePath = string.Empty;
|
||||
private string _keyFilePath = string.Empty;
|
||||
private string _password = string.Empty;
|
||||
private string _confirmPassword = string.Empty;
|
||||
private bool _useKeyFile = true;
|
||||
private bool _usePassword;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NewStoreDialogViewModel"/> class.
|
||||
@@ -50,64 +46,6 @@ public class NewStoreDialogViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password for store encryption.
|
||||
/// </summary>
|
||||
public string Password
|
||||
{
|
||||
get => _password;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _password, value))
|
||||
NotifyValidationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password confirmation value.
|
||||
/// </summary>
|
||||
public string ConfirmPassword
|
||||
{
|
||||
get => _confirmPassword;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _confirmPassword, value))
|
||||
NotifyValidationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use key file for store encryption.
|
||||
/// </summary>
|
||||
public bool UseKeyFile
|
||||
{
|
||||
get => _useKeyFile;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _useKeyFile, value))
|
||||
{
|
||||
if (value) UsePassword = false;
|
||||
NotifyValidationChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use password for store encryption.
|
||||
/// </summary>
|
||||
public bool UsePassword
|
||||
{
|
||||
get => _usePassword;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _usePassword, value))
|
||||
{
|
||||
if (value) UseKeyFile = false;
|
||||
NotifyValidationChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifyValidationChanged()
|
||||
{
|
||||
OnPropertyChanged(nameof(IsValid));
|
||||
@@ -127,22 +65,7 @@ public class NewStoreDialogViewModel : ViewModelBase
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the dialog input is valid.
|
||||
/// </summary>
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(StorePath))
|
||||
return false;
|
||||
|
||||
if (UseKeyFile)
|
||||
return !string.IsNullOrWhiteSpace(KeyFilePath);
|
||||
|
||||
if (UsePassword)
|
||||
return !string.IsNullOrWhiteSpace(Password) && Password == ConfirmPassword;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool IsValid => !string.IsNullOrWhiteSpace(StorePath) && !string.IsNullOrWhiteSpace(KeyFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the validation error message, or null if valid.
|
||||
@@ -154,18 +77,9 @@ public class NewStoreDialogViewModel : ViewModelBase
|
||||
if (string.IsNullOrWhiteSpace(StorePath))
|
||||
return DialogStrings.StorePathRequired;
|
||||
|
||||
if (UseKeyFile && string.IsNullOrWhiteSpace(KeyFilePath))
|
||||
if (string.IsNullOrWhiteSpace(KeyFilePath))
|
||||
return DialogStrings.KeyFilePathRequired;
|
||||
|
||||
if (UsePassword)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Password))
|
||||
return DialogStrings.PasswordRequired;
|
||||
|
||||
if (Password != ConfirmPassword)
|
||||
return DialogStrings.PasswordsDoNotMatch;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -217,9 +131,6 @@ public class OpenStoreDialogViewModel : ViewModelBase
|
||||
{
|
||||
private string _storePath = string.Empty;
|
||||
private string _keyFilePath = string.Empty;
|
||||
private string _password = string.Empty;
|
||||
private bool _useKeyFile = true;
|
||||
private bool _usePassword;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OpenStoreDialogViewModel"/> class.
|
||||
@@ -256,51 +167,6 @@ public class OpenStoreDialogViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the password for store decryption.
|
||||
/// </summary>
|
||||
public string Password
|
||||
{
|
||||
get => _password;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _password, value))
|
||||
NotifyValidationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use key file for store decryption.
|
||||
/// </summary>
|
||||
public bool UseKeyFile
|
||||
{
|
||||
get => _useKeyFile;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _useKeyFile, value))
|
||||
{
|
||||
if (value) UsePassword = false;
|
||||
NotifyValidationChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to use password for store decryption.
|
||||
/// </summary>
|
||||
public bool UsePassword
|
||||
{
|
||||
get => _usePassword;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _usePassword, value))
|
||||
{
|
||||
if (value) UseKeyFile = false;
|
||||
NotifyValidationChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifyValidationChanged()
|
||||
{
|
||||
OnPropertyChanged(nameof(IsValid));
|
||||
@@ -327,13 +193,7 @@ public class OpenStoreDialogViewModel : ViewModelBase
|
||||
if (string.IsNullOrWhiteSpace(StorePath))
|
||||
return false;
|
||||
|
||||
if (UseKeyFile)
|
||||
return !string.IsNullOrWhiteSpace(KeyFilePath);
|
||||
|
||||
if (UsePassword)
|
||||
return !string.IsNullOrWhiteSpace(Password);
|
||||
|
||||
return false;
|
||||
return !string.IsNullOrWhiteSpace(KeyFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,17 +210,11 @@ public class OpenStoreDialogViewModel : ViewModelBase
|
||||
if (!System.IO.File.Exists(StorePath))
|
||||
return DialogStrings.StoreFileNotFound;
|
||||
|
||||
if (UseKeyFile)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(KeyFilePath))
|
||||
return DialogStrings.KeyFilePathRequired;
|
||||
if (string.IsNullOrWhiteSpace(KeyFilePath))
|
||||
return DialogStrings.KeyFilePathRequired;
|
||||
|
||||
if (!System.IO.File.Exists(KeyFilePath))
|
||||
return DialogStrings.KeyFileNotFound;
|
||||
}
|
||||
|
||||
if (UsePassword && string.IsNullOrWhiteSpace(Password))
|
||||
return DialogStrings.PasswordRequired;
|
||||
if (!System.IO.File.Exists(KeyFilePath))
|
||||
return DialogStrings.KeyFileNotFound;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -161,30 +161,22 @@ public class MainWindowViewModel : ViewModelBase
|
||||
public ICommand ExportKeyCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new store. Called by the dialog.
|
||||
/// Creates a new store with a key file. Called by the dialog.
|
||||
/// </summary>
|
||||
/// <param name="storePath">The path where the store file will be created.</param>
|
||||
/// <param name="keyFilePath">Optional path to a key file for encryption.</param>
|
||||
/// <param name="password">Optional password for encryption.</param>
|
||||
public async Task CreateNewStoreAsync(string storePath, string? keyFilePath, string? password)
|
||||
/// <param name="keyFilePath">The path to a key file for encryption.</param>
|
||||
public async Task CreateNewStoreAsync(string storePath, string keyFilePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(keyFilePath))
|
||||
if (string.IsNullOrEmpty(keyFilePath))
|
||||
{
|
||||
_storeManager.CreateStore(storePath, keyFilePath);
|
||||
StatusMessage = $"Created store with key file: {keyFilePath}";
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
_storeManager.CreateStoreWithPassword(storePath, password);
|
||||
StatusMessage = "Created password-protected store";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Either key file path or password must be provided.");
|
||||
throw new ArgumentException("Key file path must be provided.");
|
||||
}
|
||||
|
||||
_storeManager.CreateStore(storePath, keyFilePath);
|
||||
StatusMessage = $"Created store with key file: {keyFilePath}";
|
||||
|
||||
RefreshSecrets();
|
||||
NotifyStoreChanged();
|
||||
}
|
||||
@@ -198,30 +190,22 @@ public class MainWindowViewModel : ViewModelBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens an existing store. Called by the dialog.
|
||||
/// Opens an existing store with a key file. Called by the dialog.
|
||||
/// </summary>
|
||||
/// <param name="storePath">The path to the store file to open.</param>
|
||||
/// <param name="keyFilePath">Optional path to a key file for decryption.</param>
|
||||
/// <param name="password">Optional password for decryption.</param>
|
||||
public async Task OpenExistingStoreAsync(string storePath, string? keyFilePath, string? password)
|
||||
/// <param name="keyFilePath">The path to a key file for decryption.</param>
|
||||
public async Task OpenExistingStoreAsync(string storePath, string keyFilePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(keyFilePath))
|
||||
if (string.IsNullOrEmpty(keyFilePath))
|
||||
{
|
||||
_storeManager.OpenStore(storePath, keyFilePath);
|
||||
StatusMessage = "Opened store with key file";
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(password))
|
||||
{
|
||||
_storeManager.OpenStoreWithPassword(storePath, password);
|
||||
StatusMessage = "Opened password-protected store";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Either key file path or password must be provided.");
|
||||
throw new ArgumentException("Key file path must be provided.");
|
||||
}
|
||||
|
||||
_storeManager.OpenStore(storePath, keyFilePath);
|
||||
StatusMessage = "Opened store with key file";
|
||||
|
||||
RefreshSecrets();
|
||||
NotifyStoreChanged();
|
||||
}
|
||||
|
||||
@@ -61,10 +61,7 @@ public partial class MainWindow : Window
|
||||
if (result == true)
|
||||
{
|
||||
var vm = dialog.ViewModel;
|
||||
await ViewModel.CreateNewStoreAsync(
|
||||
vm.StorePath,
|
||||
vm.UseKeyFile ? vm.KeyFilePath : null,
|
||||
vm.UsePassword ? vm.Password : null);
|
||||
await ViewModel.CreateNewStoreAsync(vm.StorePath, vm.KeyFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,10 +74,7 @@ public partial class MainWindow : Window
|
||||
if (result == true)
|
||||
{
|
||||
var vm = dialog.ViewModel;
|
||||
await ViewModel.OpenExistingStoreAsync(
|
||||
vm.StorePath,
|
||||
vm.UseKeyFile ? vm.KeyFilePath : null,
|
||||
vm.UsePassword ? vm.Password : null);
|
||||
await ViewModel.OpenExistingStoreAsync(vm.StorePath, vm.KeyFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
xmlns:vm="clr-namespace:JdeScoping.SecureStoreManager.ViewModels"
|
||||
x:Class="JdeScoping.SecureStoreManager.Views.NewStoreDialog"
|
||||
Title="Create New Store"
|
||||
Height="400" Width="500"
|
||||
Height="280" Width="500"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
CanResize="False"
|
||||
ShowInTaskbar="False">
|
||||
<!-- DataContext is set in code-behind -->
|
||||
|
||||
<Grid Margin="15" RowDefinitions="Auto,Auto,Auto,*,Auto">
|
||||
<Grid Margin="15" RowDefinitions="Auto,Auto,*,Auto">
|
||||
<!-- Store Path -->
|
||||
<Border Grid.Row="0" BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
@@ -26,25 +26,13 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Encryption Method -->
|
||||
<Border Grid.Row="1" BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Encryption Method" FontWeight="SemiBold" Margin="0,0,0,10" />
|
||||
<RadioButton Content="Use Key File (recommended for production)"
|
||||
IsChecked="{Binding UseKeyFile, Mode=TwoWay}"
|
||||
Margin="0,5" />
|
||||
<RadioButton Content="Use Password"
|
||||
IsChecked="{Binding UsePassword, Mode=TwoWay}"
|
||||
Margin="0,5" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Key File Settings -->
|
||||
<Border Grid.Row="2"
|
||||
BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10"
|
||||
IsVisible="{Binding UseKeyFile}">
|
||||
<Border Grid.Row="1"
|
||||
BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Key File" FontWeight="SemiBold" Margin="0,0,0,10" />
|
||||
<TextBlock Text="The key file is required to encrypt and decrypt the store."
|
||||
FontSize="11" Foreground="Gray" Margin="0,0,0,10" TextWrapping="Wrap" />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBox Grid.Column="0"
|
||||
Text="{Binding KeyFilePath, Mode=TwoWay}"
|
||||
@@ -57,31 +45,8 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Password Settings -->
|
||||
<Border Grid.Row="2"
|
||||
BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10"
|
||||
IsVisible="{Binding UsePassword}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Password" FontWeight="SemiBold" Margin="0,0,0,10" />
|
||||
<Grid ColumnDefinitions="100,*" RowDefinitions="Auto,Auto">
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Password:" VerticalAlignment="Center" Margin="0,5" />
|
||||
<TextBox Grid.Row="0" Grid.Column="1"
|
||||
x:Name="PasswordBox"
|
||||
PasswordChar="*"
|
||||
Text="{Binding Password, Mode=TwoWay}"
|
||||
Margin="0,5" />
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Confirm:" VerticalAlignment="Center" Margin="0,5" />
|
||||
<TextBox Grid.Row="1" Grid.Column="1"
|
||||
x:Name="ConfirmPasswordBox"
|
||||
PasswordChar="*"
|
||||
Text="{Binding ConfirmPassword, Mode=TwoWay}"
|
||||
Margin="0,5" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Validation Error -->
|
||||
<TextBlock Grid.Row="3"
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="{Binding ValidationError}"
|
||||
Foreground="Red"
|
||||
FontSize="11"
|
||||
@@ -90,7 +55,7 @@
|
||||
VerticalAlignment="Top" />
|
||||
|
||||
<!-- Buttons -->
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="10">
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="10">
|
||||
<Button Content="Create" Click="CreateButton_Click" IsEnabled="{Binding IsValid}" MinWidth="80" Padding="10,5" />
|
||||
<Button Content="Cancel" Click="CancelButton_Click" MinWidth="80" Padding="10,5" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
xmlns:vm="clr-namespace:JdeScoping.SecureStoreManager.ViewModels"
|
||||
x:Class="JdeScoping.SecureStoreManager.Views.OpenStoreDialog"
|
||||
Title="Open Store"
|
||||
Height="370" Width="500"
|
||||
Height="280" Width="500"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
CanResize="False"
|
||||
ShowInTaskbar="False">
|
||||
<!-- DataContext is set in code-behind -->
|
||||
|
||||
<Grid Margin="15" RowDefinitions="Auto,Auto,Auto,*,Auto">
|
||||
<Grid Margin="15" RowDefinitions="Auto,Auto,*,Auto">
|
||||
<!-- Store Path -->
|
||||
<Border Grid.Row="0" BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
@@ -26,25 +26,13 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Decryption Method -->
|
||||
<Border Grid.Row="1" BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Decryption Method" FontWeight="SemiBold" Margin="0,0,0,10" />
|
||||
<RadioButton Content="Use Key File"
|
||||
IsChecked="{Binding UseKeyFile, Mode=TwoWay}"
|
||||
Margin="0,5" />
|
||||
<RadioButton Content="Use Password"
|
||||
IsChecked="{Binding UsePassword, Mode=TwoWay}"
|
||||
Margin="0,5" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Key File Settings -->
|
||||
<Border Grid.Row="2"
|
||||
BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10"
|
||||
IsVisible="{Binding UseKeyFile}">
|
||||
<Border Grid.Row="1"
|
||||
BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Key File" FontWeight="SemiBold" Margin="0,0,0,10" />
|
||||
<TextBlock Text="Select the key file used to encrypt this store."
|
||||
FontSize="11" Foreground="Gray" Margin="0,0,0,10" TextWrapping="Wrap" />
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<TextBox Grid.Column="0"
|
||||
Text="{Binding KeyFilePath, Mode=TwoWay}"
|
||||
@@ -57,25 +45,8 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Password Settings -->
|
||||
<Border Grid.Row="2"
|
||||
BorderBrush="Gray" BorderThickness="1" CornerRadius="3" Padding="10" Margin="0,0,0,10"
|
||||
IsVisible="{Binding UsePassword}">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Password" FontWeight="SemiBold" Margin="0,0,0,10" />
|
||||
<Grid ColumnDefinitions="100,*">
|
||||
<TextBlock Text="Password:" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Column="1"
|
||||
x:Name="PasswordBox"
|
||||
PasswordChar="*"
|
||||
Text="{Binding Password, Mode=TwoWay}"
|
||||
Margin="0,5" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Validation Error -->
|
||||
<TextBlock Grid.Row="3"
|
||||
<TextBlock Grid.Row="2"
|
||||
Text="{Binding ValidationError}"
|
||||
Foreground="Red"
|
||||
FontSize="11"
|
||||
@@ -84,7 +55,7 @@
|
||||
VerticalAlignment="Top" />
|
||||
|
||||
<!-- Buttons -->
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="10">
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="10">
|
||||
<Button Content="Open" Click="OpenButton_Click" IsEnabled="{Binding IsValid}" MinWidth="80" Padding="10,5" />
|
||||
<Button Content="Cancel" Click="CancelButton_Click" MinWidth="80" Padding="10,5" />
|
||||
</StackPanel>
|
||||
|
||||
+3
-221
@@ -5,17 +5,6 @@ namespace JdeScoping.ConfigManager.Tests.ViewModels.Dialogs;
|
||||
|
||||
public class NewStoreDialogViewModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_DefaultsToUseKeyFile()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
|
||||
// Assert
|
||||
sut.UseKeyFile.ShouldBeTrue();
|
||||
sut.UsePassword.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StorePath_WhenEmpty_IsValidReturnsFalse()
|
||||
{
|
||||
@@ -60,13 +49,12 @@ public class NewStoreDialogViewModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenRequiredAndEmpty_IsValidReturnsFalse()
|
||||
public void KeyFilePath_WhenEmpty_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
StorePath = "/path/to/store.secure",
|
||||
UseKeyFile = true,
|
||||
KeyFilePath = ""
|
||||
};
|
||||
|
||||
@@ -75,13 +63,12 @@ public class NewStoreDialogViewModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenRequiredAndEmpty_ValidationErrorReturnsKeyFilePathRequired()
|
||||
public void KeyFilePath_WhenEmpty_ValidationErrorReturnsKeyFilePathRequired()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
StorePath = "/path/to/store.secure",
|
||||
UseKeyFile = true,
|
||||
KeyFilePath = ""
|
||||
};
|
||||
|
||||
@@ -90,13 +77,12 @@ public class NewStoreDialogViewModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenRequiredAndProvided_IsValidReturnsTrue()
|
||||
public void KeyFilePath_WhenProvided_IsValidReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
StorePath = "/path/to/store.secure",
|
||||
UseKeyFile = true,
|
||||
KeyFilePath = "/path/to/key.key"
|
||||
};
|
||||
|
||||
@@ -105,134 +91,6 @@ public class NewStoreDialogViewModelTests
|
||||
sut.ValidationError.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_WhenRequiredAndEmpty_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
StorePath = "/path/to/store.secure",
|
||||
UsePassword = true,
|
||||
Password = ""
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_WhenRequiredAndEmpty_ValidationErrorReturnsPasswordRequired()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
StorePath = "/path/to/store.secure",
|
||||
UsePassword = true,
|
||||
Password = ""
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.PasswordRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConfirmPassword_WhenRequiredAndDoesNotMatch_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
StorePath = "/path/to/store.secure",
|
||||
UsePassword = true,
|
||||
Password = "password123",
|
||||
ConfirmPassword = "differentPassword"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConfirmPassword_WhenRequiredAndDoesNotMatch_ValidationErrorReturnsPasswordsDoNotMatch()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
StorePath = "/path/to/store.secure",
|
||||
UsePassword = true,
|
||||
Password = "password123",
|
||||
ConfirmPassword = "differentPassword"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.PasswordsDoNotMatch);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_WhenRequiredAndMatchesConfirmPassword_IsValidReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
StorePath = "/path/to/store.secure",
|
||||
UsePassword = true,
|
||||
Password = "password123",
|
||||
ConfirmPassword = "password123"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeTrue();
|
||||
sut.ValidationError.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseKeyFile_WhenSetToTrue_SetsUsePasswordToFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
UsePassword = true
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.UseKeyFile = true;
|
||||
|
||||
// Assert
|
||||
sut.UseKeyFile.ShouldBeTrue();
|
||||
sut.UsePassword.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsePassword_WhenSetToTrue_SetsUseKeyFileToFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
UseKeyFile = true
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.UsePassword = true;
|
||||
|
||||
// Assert
|
||||
sut.UsePassword.ShouldBeTrue();
|
||||
sut.UseKeyFile.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WhenNeitherKeyFileNorPasswordSelected_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel
|
||||
{
|
||||
StorePath = "/path/to/store.secure"
|
||||
};
|
||||
// Manually set both to false (shouldn't happen in UI, but test edge case)
|
||||
sut.UseKeyFile = false;
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StorePath_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
@@ -309,82 +167,6 @@ public class NewStoreDialogViewModelTests
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(NewStoreDialogViewModel.Password))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.Password = "newpassword";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConfirmPassword_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(NewStoreDialogViewModel.ConfirmPassword))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.ConfirmPassword = "newpassword";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseKeyFile_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel { UseKeyFile = false };
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(NewStoreDialogViewModel.UseKeyFile))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.UseKeyFile = true;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsePassword_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(NewStoreDialogViewModel.UsePassword))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.UsePassword = true;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Commands_AreInitialized()
|
||||
{
|
||||
|
||||
+5
-182
@@ -25,17 +25,6 @@ public class UnlockStoreDialogViewModelTests
|
||||
Should.Throw<ArgumentNullException>(() => new UnlockStoreDialogViewModel(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_DefaultsToUseKeyFile()
|
||||
{
|
||||
// Arrange & Act
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
|
||||
// Assert
|
||||
sut.UseKeyFile.ShouldBeTrue();
|
||||
sut.UsePassword.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StorePath_IsReadOnly()
|
||||
{
|
||||
@@ -47,12 +36,11 @@ public class UnlockStoreDialogViewModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenRequiredAndEmpty_IsValidReturnsFalse()
|
||||
public void KeyFilePath_WhenEmpty_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UseKeyFile = true,
|
||||
KeyFilePath = ""
|
||||
};
|
||||
|
||||
@@ -61,12 +49,11 @@ public class UnlockStoreDialogViewModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenRequiredAndEmpty_ValidationErrorReturnsKeyFilePathRequired()
|
||||
public void KeyFilePath_WhenEmpty_ValidationErrorReturnsKeyFilePathRequired()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UseKeyFile = true,
|
||||
KeyFilePath = ""
|
||||
};
|
||||
|
||||
@@ -75,12 +62,11 @@ public class UnlockStoreDialogViewModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenRequiredAndWhitespace_IsValidReturnsFalse()
|
||||
public void KeyFilePath_WhenWhitespace_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UseKeyFile = true,
|
||||
KeyFilePath = " "
|
||||
};
|
||||
|
||||
@@ -90,12 +76,11 @@ public class UnlockStoreDialogViewModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenRequiredAndFileDoesNotExist_IsValidReturnsFalse()
|
||||
public void KeyFilePath_WhenFileDoesNotExist_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UseKeyFile = true,
|
||||
KeyFilePath = "/nonexistent/path/to/key.key"
|
||||
};
|
||||
|
||||
@@ -104,12 +89,11 @@ public class UnlockStoreDialogViewModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenRequiredAndFileDoesNotExist_ValidationErrorReturnsKeyFileNotFound()
|
||||
public void KeyFilePath_WhenFileDoesNotExist_ValidationErrorReturnsKeyFileNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UseKeyFile = true,
|
||||
KeyFilePath = "/nonexistent/path/to/key.key"
|
||||
};
|
||||
|
||||
@@ -117,110 +101,6 @@ public class UnlockStoreDialogViewModelTests
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.KeyFileNotFound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_WhenRequiredAndEmpty_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UsePassword = true,
|
||||
Password = ""
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_WhenRequiredAndEmpty_ValidationErrorReturnsPasswordRequired()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UsePassword = true,
|
||||
Password = ""
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.PasswordRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_WhenRequiredAndWhitespace_IsValidReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UsePassword = true,
|
||||
Password = " "
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
sut.ValidationError.ShouldBe(SecureStoreStrings.PasswordRequired);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_WhenRequiredAndProvided_IsValidReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UsePassword = true,
|
||||
Password = "password123"
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeTrue();
|
||||
sut.ValidationError.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseKeyFile_WhenSetToTrue_SetsUsePasswordToFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UsePassword = true
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.UseKeyFile = true;
|
||||
|
||||
// Assert
|
||||
sut.UseKeyFile.ShouldBeTrue();
|
||||
sut.UsePassword.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsePassword_WhenSetToTrue_SetsUseKeyFileToFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure")
|
||||
{
|
||||
UseKeyFile = true
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.UsePassword = true;
|
||||
|
||||
// Assert
|
||||
sut.UsePassword.ShouldBeTrue();
|
||||
sut.UseKeyFile.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WhenNeitherKeyFileNorPasswordSelected_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
// Manually set both to false (shouldn't happen in UI, but test edge case)
|
||||
sut.UseKeyFile = false;
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyFilePath_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
@@ -278,63 +158,6 @@ public class UnlockStoreDialogViewModelTests
|
||||
validationErrorPropertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Password_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(UnlockStoreDialogViewModel.Password))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.Password = "newpassword";
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseKeyFile_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure") { UseKeyFile = false };
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(UnlockStoreDialogViewModel.UseKeyFile))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.UseKeyFile = true;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsePassword_WhenChanged_RaisesPropertyChanged()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new UnlockStoreDialogViewModel("/path/to/store.secure");
|
||||
var propertyChangedRaised = false;
|
||||
sut.PropertyChanged += (s, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(UnlockStoreDialogViewModel.UsePassword))
|
||||
propertyChangedRaised = true;
|
||||
};
|
||||
|
||||
// Act
|
||||
sut.UsePassword = true;
|
||||
|
||||
// Assert
|
||||
propertyChangedRaised.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BrowseKeyFilePathCommand_IsInitialized()
|
||||
{
|
||||
|
||||
@@ -61,31 +61,6 @@ public class SecureStoreManagerTests : IDisposable
|
||||
File.Exists(keyPath).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateStoreWithPassword_CreatesStore()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
|
||||
// Act
|
||||
_sut.CreateStoreWithPassword(storePath, "testpassword123");
|
||||
|
||||
// Assert
|
||||
_sut.IsStoreOpen.ShouldBeTrue();
|
||||
_sut.CurrentStorePath.ShouldBe(storePath);
|
||||
File.Exists(storePath).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateStoreWithPassword_WithEmptyPassword_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentException>(() => _sut.CreateStoreWithPassword(storePath, ""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenStore_WithValidKeyFile_OpensStore()
|
||||
{
|
||||
@@ -114,22 +89,6 @@ public class SecureStoreManagerTests : IDisposable
|
||||
Should.Throw<FileNotFoundException>(() => _sut.OpenStore(storePath, keyPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenStoreWithPassword_OpensStore()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = Path.Combine(_testDirectory, "test.json");
|
||||
var password = "testpassword123";
|
||||
_sut.CreateStoreWithPassword(storePath, password);
|
||||
_sut.CloseStore();
|
||||
|
||||
// Act
|
||||
_sut.OpenStoreWithPassword(storePath, password);
|
||||
|
||||
// Assert
|
||||
_sut.IsStoreOpen.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloseStore_ClosesOpenStore()
|
||||
{
|
||||
|
||||
@@ -12,7 +12,6 @@ public class NewStoreDialogViewModelTests
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = string.Empty;
|
||||
sut.UseKeyFile = true;
|
||||
sut.KeyFilePath = "/path/to/key.key";
|
||||
|
||||
// Act & Assert
|
||||
@@ -20,12 +19,11 @@ public class NewStoreDialogViewModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WhenUseKeyFileButKeyFilePathEmpty_ReturnsFalse()
|
||||
public void IsValid_WhenKeyFilePathEmpty_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.UseKeyFile = true;
|
||||
sut.KeyFilePath = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
@@ -33,87 +31,17 @@ public class NewStoreDialogViewModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WhenUsePasswordButPasswordEmpty_ReturnsFalse()
|
||||
public void IsValid_WithValidConfiguration_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.UsePassword = true;
|
||||
sut.Password = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WhenPasswordsDoNotMatch_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.UsePassword = true;
|
||||
sut.Password = "password123";
|
||||
sut.ConfirmPassword = "different456";
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WithValidKeyFileConfiguration_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.UseKeyFile = true;
|
||||
sut.KeyFilePath = "/path/to/key.key";
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WithValidPasswordConfiguration_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.UsePassword = true;
|
||||
sut.Password = "password123";
|
||||
sut.ConfirmPassword = "password123";
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseKeyFile_WhenSetToTrue_SetsUsePasswordToFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.UsePassword = true;
|
||||
|
||||
// Act
|
||||
sut.UseKeyFile = true;
|
||||
|
||||
// Assert
|
||||
sut.UsePassword.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsePassword_WhenSetToTrue_SetsUseKeyFileToFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.UseKeyFile = true;
|
||||
|
||||
// Act
|
||||
sut.UsePassword = true;
|
||||
|
||||
// Assert
|
||||
sut.UseKeyFile.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationError_WhenStorePathEmpty_ReturnsAppropriateMessage()
|
||||
{
|
||||
@@ -131,47 +59,18 @@ public class NewStoreDialogViewModelTests
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.UseKeyFile = true;
|
||||
sut.KeyFilePath = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe("Key file path is required.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationError_WhenPasswordEmpty_ReturnsAppropriateMessage()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.UsePassword = true;
|
||||
sut.Password = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe("Password is required.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationError_WhenPasswordsDoNotMatch_ReturnsAppropriateMessage()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.UsePassword = true;
|
||||
sut.Password = "password123";
|
||||
sut.ConfirmPassword = "different456";
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe("Passwords do not match.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationError_WhenValid_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new NewStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.UseKeyFile = true;
|
||||
sut.KeyFilePath = "/path/to/key.key";
|
||||
|
||||
// Act & Assert
|
||||
@@ -193,85 +92,17 @@ public class OpenStoreDialogViewModelTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WhenUseKeyFileButKeyFilePathEmpty_ReturnsFalse()
|
||||
public void IsValid_WhenKeyFilePathEmpty_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new OpenStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.UseKeyFile = true;
|
||||
sut.KeyFilePath = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WhenUsePasswordButPasswordEmpty_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new OpenStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.UsePassword = true;
|
||||
sut.Password = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WithValidKeyFileConfiguration_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new OpenStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.UseKeyFile = true;
|
||||
sut.KeyFilePath = "/path/to/key.key";
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValid_WithValidPasswordConfiguration_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new OpenStoreDialogViewModel();
|
||||
sut.StorePath = "/path/to/store.json";
|
||||
sut.UsePassword = true;
|
||||
sut.Password = "password123";
|
||||
|
||||
// Act & Assert
|
||||
sut.IsValid.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UseKeyFile_WhenSetToTrue_SetsUsePasswordToFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new OpenStoreDialogViewModel();
|
||||
sut.UsePassword = true;
|
||||
|
||||
// Act
|
||||
sut.UseKeyFile = true;
|
||||
|
||||
// Assert
|
||||
sut.UsePassword.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UsePassword_WhenSetToTrue_SetsUseKeyFileToFalse()
|
||||
{
|
||||
// Arrange
|
||||
var sut = new OpenStoreDialogViewModel();
|
||||
sut.UseKeyFile = true;
|
||||
|
||||
// Act
|
||||
sut.UsePassword = true;
|
||||
|
||||
// Assert
|
||||
sut.UseKeyFile.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationError_WhenStorePathEmpty_ReturnsAppropriateMessage()
|
||||
{
|
||||
@@ -303,7 +134,6 @@ public class OpenStoreDialogViewModelTests
|
||||
{
|
||||
var sut = new OpenStoreDialogViewModel();
|
||||
sut.StorePath = tempFile;
|
||||
sut.UseKeyFile = true;
|
||||
sut.KeyFilePath = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
@@ -324,7 +154,6 @@ public class OpenStoreDialogViewModelTests
|
||||
{
|
||||
var sut = new OpenStoreDialogViewModel();
|
||||
sut.StorePath = tempFile;
|
||||
sut.UseKeyFile = true;
|
||||
sut.KeyFilePath = "/nonexistent/key.key";
|
||||
|
||||
// Act & Assert
|
||||
@@ -335,27 +164,6 @@ public class OpenStoreDialogViewModelTests
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidationError_WhenPasswordEmpty_ReturnsAppropriateMessage()
|
||||
{
|
||||
// Arrange - Create temp store file so we get past that validation
|
||||
var tempFile = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
var sut = new OpenStoreDialogViewModel();
|
||||
sut.StorePath = tempFile;
|
||||
sut.UsePassword = true;
|
||||
sut.Password = string.Empty;
|
||||
|
||||
// Act & Assert
|
||||
sut.ValidationError.ShouldBe("Password is required.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SecretEditDialogViewModelTests
|
||||
|
||||
+2
-32
@@ -97,27 +97,12 @@ public class MainWindowViewModelTests
|
||||
_mockStoreManager.GetKeys().Returns(new List<string>().AsReadOnly());
|
||||
|
||||
// Act
|
||||
await _sut.CreateNewStoreAsync(storePath, keyPath, null);
|
||||
await _sut.CreateNewStoreAsync(storePath, keyPath);
|
||||
|
||||
// Assert
|
||||
_mockStoreManager.Received(1).CreateStore(storePath, keyPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateNewStoreAsync_WithPassword_CallsStoreManagerCreateStoreWithPassword()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = "/path/to/store.json";
|
||||
var password = "password123";
|
||||
_mockStoreManager.GetKeys().Returns(new List<string>().AsReadOnly());
|
||||
|
||||
// Act
|
||||
await _sut.CreateNewStoreAsync(storePath, null, password);
|
||||
|
||||
// Assert
|
||||
_mockStoreManager.Received(1).CreateStoreWithPassword(storePath, password);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OpenExistingStoreAsync_WithKeyFile_CallsStoreManagerOpenStore()
|
||||
{
|
||||
@@ -127,27 +112,12 @@ public class MainWindowViewModelTests
|
||||
_mockStoreManager.GetKeys().Returns(new List<string>().AsReadOnly());
|
||||
|
||||
// Act
|
||||
await _sut.OpenExistingStoreAsync(storePath, keyPath, null);
|
||||
await _sut.OpenExistingStoreAsync(storePath, keyPath);
|
||||
|
||||
// Assert
|
||||
_mockStoreManager.Received(1).OpenStore(storePath, keyPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OpenExistingStoreAsync_WithPassword_CallsStoreManagerOpenStoreWithPassword()
|
||||
{
|
||||
// Arrange
|
||||
var storePath = "/path/to/store.json";
|
||||
var password = "password123";
|
||||
_mockStoreManager.GetKeys().Returns(new List<string>().AsReadOnly());
|
||||
|
||||
// Act
|
||||
await _sut.OpenExistingStoreAsync(storePath, null, password);
|
||||
|
||||
// Assert
|
||||
_mockStoreManager.Received(1).OpenStoreWithPassword(storePath, password);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveSecretAsync_CallsStoreManagerSetSecret()
|
||||
{
|
||||
|
||||
@@ -45,15 +45,15 @@ public class NewStoreDialogTests
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
public void NewStoreDialog_HasRadioButtons()
|
||||
public void NewStoreDialog_HasKeyFilePathTextBox()
|
||||
{
|
||||
// Arrange & Act
|
||||
var dialog = new NewStoreDialog();
|
||||
dialog.Show();
|
||||
|
||||
// Assert
|
||||
var radioButtons = dialog.GetVisualDescendants().OfType<RadioButton>().ToList();
|
||||
radioButtons.Count.ShouldBeGreaterThanOrEqualTo(2);
|
||||
// Assert - Should have at least 2 text boxes: store path and key file path
|
||||
var textBoxes = dialog.GetVisualDescendants().OfType<TextBox>().ToList();
|
||||
textBoxes.Count.ShouldBeGreaterThanOrEqualTo(2);
|
||||
}
|
||||
|
||||
[AvaloniaFact]
|
||||
|
||||
Reference in New Issue
Block a user