Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Desktop/Desktop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<OutputType>WinExe</OutputType>
<!--If you are willing to use Windows/MacOS native APIs you will need to create 3 projects.
One for Windows with net7.0-windows TFM, one for MacOS with net7.0-macos and one with net7.0 TFM for Linux.-->
<TargetFrameworks>net9.0</TargetFrameworks>
<TargetFrameworks>net10.0</TargetFrameworks>
<!-- <TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('osx'))">$(TargetFrameworks);net8.0-macos</TargetFrameworks> -->
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.19041.0</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net10.0-windows10.0.19041.0</TargetFrameworks>
<WindowsSdkPackageVersion Condition="$([MSBuild]::IsOSPlatform('windows'))">10.0.19041.41</WindowsSdkPackageVersion>

<Nullable>enable</Nullable>
Expand Down Expand Up @@ -44,8 +44,8 @@


<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.6" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.8" />
</ItemGroup>


Expand Down
9 changes: 0 additions & 9 deletions KeyVaultExplorer/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ public static void CreateDesktopResources()

Dispatcher.UIThread.Post(async () =>
{
await KvExplorerDb.OpenSqlConnection();

if (!dbExists)
KvExplorerDb.InitializeDatabase();
}, DispatcherPriority.Loaded);
Expand All @@ -52,13 +50,6 @@ public static void CreateDesktopResources()
}
}

private void MainWindowOnClosing(object? sender, WindowClosingEventArgs e)
{
if (sender is Window window)
{
KvExplorerDb.CloseSqlConnection();
}
}

public override void Initialize()
{
Expand Down
2 changes: 1 addition & 1 deletion KeyVaultExplorer/Assets/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<key>CFBundleShortVersionString</key>
<string>0.0.0.2</string>
<key>CFBundleIdentifier</key>
<string>us.sidesteplabs.KeyVaultExplorer</string>
<string>us.cricketthomas.KeyVaultExplorer</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>NSAppTransportSecurity</key>
Expand Down
272 changes: 148 additions & 124 deletions KeyVaultExplorer/Database/KvExplorerDb.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using KeyVaultExplorer.Models;
using KeyVaultExplorer.Database;
using KeyVaultExplorer.Models;
using KeyVaultExplorer.Services;
using Microsoft.Data.Sqlite;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.IO;
using System.Linq;
using System.Text;
Expand All @@ -13,72 +13,37 @@ namespace KeyVaultExplorer.Database;

public partial class KvExplorerDb : IDisposable
{
private static DbConnection _connection;
private static string _password = null;

public KvExplorerDb()
{
}

public static async Task OpenSqlConnection()
public async void Dispose()
{
string DataSource = Path.Combine(Constants.DatabaseFilePath);
var pass = await DatabaseEncryptedPasswordManager.GetSecret();
var connection = new SqliteConnection($"Filename={DataSource}; Password={pass}");
connection.Open();
_connection = connection;
using var connection = await TryCreateDatabaseAndOpenConnection();
await connection.CloseAsync();
}

public static void CloseSqlConnection()
public async Task<bool> DeleteQuickAccessItemByKeyVaultId(string keyVaultId)
{
_connection.Close();
}
using var connection = await TryCreateDatabaseAndOpenConnection();
await connection.OpenAsync();
var command = connection.CreateCommand();
command.CommandText = "DELETE FROM QuickAccess WHERE KeyVaultId = @KeyVaultId;";
command.Parameters.Add(new SqliteParameter("@KeyVaultId", keyVaultId));

public void Dispose()
{
_connection.Close();
}
var rowsAffected = await command.ExecuteNonQueryAsync();

public static async void InitializeDatabase()
{
string tableCommand = """
PRAGMA foreign_keys = off;
BEGIN TRANSACTION;
-- Table: Subscriptions
CREATE TABLE IF NOT EXISTS Subscriptions (
DisplayName TEXT NOT NULL,
SubscriptionId TEXT (200) PRIMARY KEY UNIQUE ON CONFLICT IGNORE,
TenantId TEXT (200)
);
CREATE UNIQUE INDEX IF NOT EXISTS IX_Subscriptions_DisplayName_SubscriptionsId ON Subscriptions (
SubscriptionId ASC,
DisplayName ASC
);
-- Table: QuickAccess
CREATE TABLE IF NOT EXISTS QuickAccess (
Id INTEGER NOT NULL CONSTRAINT PK_QuickAccess PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL,
VaultUri TEXT NOT NULL,
KeyVaultId TEXT NOT NULL CONSTRAINT UQ_KeyVaultId UNIQUE ON CONFLICT IGNORE,
SubscriptionDisplayName TEXT,
SubscriptionId TEXT,
TenantId TEXT NOT NULL,
Location TEXT NOT NULL
);
-- Index: IX_QuickAccess_KeyVaultId
CREATE INDEX IF NOT EXISTS IX_QuickAccess_KeyVaultId ON QuickAccess (
KeyVaultId
);
COMMIT TRANSACTION;
PRAGMA foreign_keys = on;
""";
var createTableCommand = _connection.CreateCommand();
createTableCommand.CommandText = tableCommand;
await createTableCommand.ExecuteNonQueryAsync();
// Check if any rows were deleted (1 or more indicates success)
return rowsAffected > 0;
}

public async IAsyncEnumerable<QuickAccess> GetQuickAccessItemsAsyncEnumerable(string tenantId = null)
{
var command = _connection.CreateCommand();
using var connection = await TryCreateDatabaseAndOpenConnection();
await connection.OpenAsync();
var command = connection.CreateCommand();
var query = new StringBuilder("SELECT Id, Name, VaultUri, KeyVaultId, SubscriptionDisplayName, SubscriptionId, TenantId, Location FROM QuickAccess");

if (!string.IsNullOrWhiteSpace(tenantId))
Expand All @@ -88,7 +53,6 @@ public async IAsyncEnumerable<QuickAccess> GetQuickAccessItemsAsyncEnumerable(st
query.Append(";");
command.CommandText = query.ToString();


var reader = await command.ExecuteReaderAsync();

while (await reader.ReadAsync())
Expand All @@ -108,76 +72,13 @@ public async IAsyncEnumerable<QuickAccess> GetQuickAccessItemsAsyncEnumerable(st
}
}

public async Task<bool> QuickAccessItemByKeyVaultIdExists(string? keyVaultId)
{
var command = _connection.CreateCommand();
command.CommandText = "SELECT 1 FROM QuickAccess WHERE KeyVaultId = @KeyVaultId LIMIT 1;";
command.Parameters.Add(new SqliteParameter("@KeyVaultId", keyVaultId));

var result = await command.ExecuteScalarAsync();
return result is not null;
}

public async Task<bool> DeleteQuickAccessItemByKeyVaultId(string keyVaultId)
{
var command = _connection.CreateCommand();
command.CommandText = "DELETE FROM QuickAccess WHERE KeyVaultId = @KeyVaultId;";
command.Parameters.Add(new SqliteParameter("@KeyVaultId", keyVaultId));

var rowsAffected = await command.ExecuteNonQueryAsync();

// Check if any rows were deleted (1 or more indicates success)
return rowsAffected > 0;
}

public async Task InsertQuickAccessItemAsync(QuickAccess item)
{
var command = _connection.CreateCommand();
command.CommandText = """
INSERT INTO QuickAccess (Name, VaultUri, KeyVaultId, SubscriptionDisplayName, SubscriptionId, TenantId, Location)
VALUES (@Name, @VaultUri, @KeyVaultId, @SubscriptionDisplayName, @SubscriptionId, @TenantId, @Location);
""";
command.Parameters.Add(new SqliteParameter("@Name", item.Name));
command.Parameters.Add(new SqliteParameter("@VaultUri", item.VaultUri));
command.Parameters.Add(new SqliteParameter("@KeyVaultId", item.KeyVaultId));
command.Parameters.Add(new SqliteParameter("@SubscriptionDisplayName", item.SubscriptionDisplayName ?? (object)DBNull.Value));
command.Parameters.Add(new SqliteParameter("@SubscriptionId", item.SubscriptionId ?? (object)DBNull.Value));
command.Parameters.Add(new SqliteParameter("@TenantId", item.TenantId));
command.Parameters.Add(new SqliteParameter("@Location", item.Location));
await command.ExecuteNonQueryAsync();
}

public async Task<AppSettings> GetToggleSettings()
{
var command = _connection.CreateCommand();
command.CommandText = "SELECT Name, Value FROM SETTINGS";
var settings = new AppSettings();
var reader = command.ExecuteReader();
while (reader.Read())
{
Enum.TryParse(reader.GetString(0), true, out SettingType parsedEnumValue);
switch (parsedEnumValue)
{
case SettingType.BackgroundTransparency:
settings.BackgroundTransparency = reader.GetBoolean(1);
break;

case SettingType.ClipboardTimeout:
settings.ClipboardTimeout = reader.GetInt32(1);
break;

default:
break;
}
}
return settings;
}

public async Task<List<Subscriptions>> GetStoredSubscriptions(string tenantId = null)
{
var command = _connection.CreateCommand();
using var connection = await TryCreateDatabaseAndOpenConnection();
await connection.OpenAsync();
var command = connection.CreateCommand();
var query = new StringBuilder("SELECT DisplayName, SubscriptionId, TenantId FROM Subscriptions");

if (!string.IsNullOrWhiteSpace(tenantId))
{
query.Append($" WHERE TenantId = '{tenantId.ToUpperInvariant()}'");
Expand All @@ -200,22 +101,60 @@ public async Task<List<Subscriptions>> GetStoredSubscriptions(string tenantId =
return subscriptions;
}

public async Task InsertSubscriptions(IList<Subscriptions> subscriptions)
public async Task InsertQuickAccessItemAsync(QuickAccess item)
{
using var connection = await TryCreateDatabaseAndOpenConnection();
await connection.OpenAsync();
var command = connection.CreateCommand();
command.CommandText = """
INSERT INTO QuickAccess (Name, VaultUri, KeyVaultId, SubscriptionDisplayName, SubscriptionId, TenantId, Location)
VALUES (@Name, @VaultUri, @KeyVaultId, @SubscriptionDisplayName, @SubscriptionId, @TenantId, @Location);
""";
command.Parameters.Add(new SqliteParameter("@Name", item.Name));
command.Parameters.Add(new SqliteParameter("@VaultUri", item.VaultUri));
command.Parameters.Add(new SqliteParameter("@KeyVaultId", item.KeyVaultId));
command.Parameters.Add(new SqliteParameter("@SubscriptionDisplayName", item.SubscriptionDisplayName ?? (object)DBNull.Value));
command.Parameters.Add(new SqliteParameter("@SubscriptionId", item.SubscriptionId ?? (object)DBNull.Value));
command.Parameters.Add(new SqliteParameter("@TenantId", item.TenantId));
command.Parameters.Add(new SqliteParameter("@Location", item.Location));
await command.ExecuteNonQueryAsync();
}

public async Task InsertSubscriptions(IEnumerable<Subscriptions> subscriptions)
{
using var connection = await TryCreateDatabaseAndOpenConnection();
await connection.OpenAsync();
using var tx = connection.BeginTransaction();

foreach (var subscription in subscriptions)
{
var command = _connection.CreateCommand();
var command = connection.CreateCommand();
command.CommandText = "INSERT OR IGNORE INTO Subscriptions (DisplayName, SubscriptionId, TenantId) VALUES (@DisplayName, @SubscriptionId, @TenantId);";
command.Parameters.Add(new SqliteParameter("@DisplayName", subscription.DisplayName));
command.Parameters.Add(new SqliteParameter("@SubscriptionId", subscription.SubscriptionId));
command.Parameters.Add(new SqliteParameter("@TenantId", subscription.TenantId));
await command.ExecuteNonQueryAsync();
}
tx.Commit();
}

public async Task<bool> QuickAccessItemByKeyVaultIdExists(string? keyVaultId)
{
using var connection = await TryCreateDatabaseAndOpenConnection();
await connection.OpenAsync();
var command = connection.CreateCommand();
command.CommandText = "SELECT 1 FROM QuickAccess WHERE KeyVaultId = @KeyVaultId LIMIT 1;";
command.Parameters.Add(new SqliteParameter("@KeyVaultId", keyVaultId));

var result = await command.ExecuteScalarAsync();
return result is not null;
}

public async Task RemoveSubscriptionsBySubscriptionIDs(IEnumerable<string> subscriptionIds)
{
var command = _connection.CreateCommand();
using var connection = await TryCreateDatabaseAndOpenConnection();
await connection.OpenAsync();
var command = connection.CreateCommand();
var paramString = new StringBuilder("DELETE FROM Subscriptions WHERE SubscriptionId IN (");

subscriptionIds.TryGetNonEnumeratedCount(out int count);
Expand All @@ -231,4 +170,89 @@ public async Task RemoveSubscriptionsBySubscriptionIDs(IEnumerable<string> subsc

await command.ExecuteNonQueryAsync();
}

private static async Task<SqliteConnection> TryCreateDatabaseAndOpenConnection()
{
var dbPassExists = File.Exists(Constants.DatabasePasswordFilePath);
if (!dbPassExists)
{
DatabaseEncryptedPasswordManager.SetSecret($"keyvaultexplorer_{System.Guid.NewGuid().ToString()[..6]}");
}

_password = await DatabaseEncryptedPasswordManager.GetSecret();

var db = new SqliteConnection($"Filename={Constants.DatabaseFilePath}; Password={_password}");

return db;
}

public static async void InitializeDatabase()
{
using var connection = await TryCreateDatabaseAndOpenConnection();
await connection.OpenAsync();

string tableCommand = """
PRAGMA foreign_keys = off;
BEGIN TRANSACTION;
-- Table: Subscriptions
CREATE TABLE IF NOT EXISTS Subscriptions (
DisplayName TEXT NOT NULL,
SubscriptionId TEXT (200) PRIMARY KEY UNIQUE ON CONFLICT IGNORE,
TenantId TEXT (200)
);
CREATE UNIQUE INDEX IF NOT EXISTS IX_Subscriptions_DisplayName_SubscriptionsId ON Subscriptions (
SubscriptionId ASC,
DisplayName ASC
);
-- Table: QuickAccess
CREATE TABLE IF NOT EXISTS QuickAccess (
Id INTEGER NOT NULL CONSTRAINT PK_QuickAccess PRIMARY KEY AUTOINCREMENT,
Name TEXT NOT NULL,
VaultUri TEXT NOT NULL,
KeyVaultId TEXT NOT NULL CONSTRAINT UQ_KeyVaultId UNIQUE ON CONFLICT IGNORE,
SubscriptionDisplayName TEXT,
SubscriptionId TEXT,
TenantId TEXT NOT NULL,
Location TEXT NOT NULL
);
-- Index: IX_QuickAccess_KeyVaultId
CREATE INDEX IF NOT EXISTS IX_QuickAccess_KeyVaultId ON QuickAccess (
KeyVaultId
);
COMMIT TRANSACTION;
PRAGMA foreign_keys = on;
""";
var createTableCommand = connection.CreateCommand();
createTableCommand.CommandText = tableCommand;
await createTableCommand.ExecuteNonQueryAsync();
}

public async Task DropTablesAndRecreate()
{
using var connection = await TryCreateDatabaseAndOpenConnection();
await connection.OpenAsync();
string deleteCommand = """
PRAGMA foreign_keys = off;
BEGIN TRANSACTION;
DROP TABLE IF EXISTS Subscriptions;
DROP TABLE IF EXISTS QuickAccess;
COMMIT TRANSACTION;
PRAGMA foreign_keys = on;
""";
var deleteTableCommand = connection.CreateCommand();
deleteTableCommand.CommandText = deleteCommand;
await deleteTableCommand.ExecuteNonQueryAsync();
InitializeDatabase();
}

public async Task DeleteDatabaseFile()
{
if (File.Exists(Constants.DatabaseFilePath))
{
using var connection = await TryCreateDatabaseAndOpenConnection();
await connection.CloseAsync();
File.Delete(Constants.DatabaseFilePath);
DatabaseEncryptedPasswordManager.PurgePasswords();
}
}
}
Loading
Loading