Files

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);
}
}