80 lines
3.0 KiB
C#
80 lines
3.0 KiB
C#
using System.Security.AccessControl;
|
|
using System.Security.Principal;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using WxAgent.Core;
|
|
|
|
namespace WxAgent.Windows;
|
|
|
|
public static class DatabaseKeyStore
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = CreateJsonOptions();
|
|
|
|
public static string DefaultPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "WxAgent", "database-keys.json");
|
|
|
|
public static async Task SaveAsync(IReadOnlyList<AccountKeySet> accounts, string? path, CancellationToken cancellationToken)
|
|
{
|
|
var fullPath = Path.GetFullPath(path ?? DefaultPath);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
|
|
var temporary = fullPath + ".tmp-" + Guid.NewGuid().ToString("N");
|
|
try
|
|
{
|
|
await using (new FileStream(temporary, FileMode.CreateNew, FileAccess.Write, FileShare.None))
|
|
{
|
|
}
|
|
|
|
RestrictToCurrentUser(temporary);
|
|
await using (var stream = new FileStream(temporary, FileMode.Truncate, FileAccess.Write, FileShare.None, 81920, FileOptions.Asynchronous | FileOptions.WriteThrough))
|
|
{
|
|
await JsonSerializer.SerializeAsync(stream, accounts, JsonOptions, cancellationToken);
|
|
await stream.FlushAsync(cancellationToken);
|
|
}
|
|
|
|
File.Move(temporary, fullPath, true);
|
|
RestrictToCurrentUser(fullPath);
|
|
}
|
|
finally
|
|
{
|
|
if (File.Exists(temporary))
|
|
{
|
|
File.Delete(temporary);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static async Task<IReadOnlyList<AccountKeySet>> LoadAsync(string? path, CancellationToken cancellationToken)
|
|
{
|
|
var fullPath = Path.GetFullPath(path ?? DefaultPath);
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
if (!File.Exists(fullPath))
|
|
{
|
|
return [];
|
|
}
|
|
|
|
await using var stream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 81920, FileOptions.Asynchronous);
|
|
return await JsonSerializer.DeserializeAsync<List<AccountKeySet>>(stream, JsonOptions, cancellationToken) ?? [];
|
|
}
|
|
|
|
private static JsonSerializerOptions CreateJsonOptions()
|
|
{
|
|
var options = new JsonSerializerOptions { WriteIndented = true };
|
|
options.Converters.Add(new JsonStringEnumConverter());
|
|
return options;
|
|
}
|
|
|
|
internal static void RestrictToCurrentUser(string path)
|
|
{
|
|
if (!OperatingSystem.IsWindows())
|
|
{
|
|
return;
|
|
}
|
|
|
|
var identity = WindowsIdentity.GetCurrent().User ?? throw new InvalidOperationException("Current Windows user SID is unavailable.");
|
|
var security = new FileSecurity();
|
|
security.SetOwner(identity);
|
|
security.SetAccessRuleProtection(isProtected: true, preserveInheritance: false);
|
|
security.AddAccessRule(new FileSystemAccessRule(identity, FileSystemRights.FullControl, AccessControlType.Allow));
|
|
new FileInfo(path).SetAccessControl(security);
|
|
}
|
|
}
|