Config: Improve account model
This commit is contained in:
parent
16a17e0dcf
commit
550fa92a53
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
@ -16,7 +17,7 @@ namespace Pal.Client.Configuration
|
|||||||
public AccountConfigurationV7(string server, Guid accountId)
|
public AccountConfigurationV7(string server, Guid accountId)
|
||||||
{
|
{
|
||||||
Server = server;
|
Server = server;
|
||||||
EncryptedId = EncryptAccountId(accountId);
|
(EncryptedId, Entropy, Format) = EncryptAccountId(accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Obsolete("for V1 import")]
|
[Obsolete("for V1 import")]
|
||||||
@ -25,69 +26,111 @@ namespace Pal.Client.Configuration
|
|||||||
Server = server;
|
Server = server;
|
||||||
|
|
||||||
if (accountId.StartsWith("s:"))
|
if (accountId.StartsWith("s:"))
|
||||||
EncryptedId = accountId;
|
{
|
||||||
|
EncryptedId = accountId.Substring(2);
|
||||||
|
Entropy = ConfigurationData.FixedV1Entropy;
|
||||||
|
Format = EFormat.UseProtectedData;
|
||||||
|
|
||||||
|
// try to migrate away from v1 entropy if possible
|
||||||
|
Guid? decryptedId = DecryptAccountId();
|
||||||
|
if (decryptedId != null)
|
||||||
|
(EncryptedId, Entropy, Format) = EncryptAccountId(decryptedId.Value);
|
||||||
|
}
|
||||||
else if (Guid.TryParse(accountId, out Guid guid))
|
else if (Guid.TryParse(accountId, out Guid guid))
|
||||||
EncryptedId = EncryptAccountId(guid);
|
(EncryptedId, Entropy, Format) = EncryptAccountId(guid);
|
||||||
else
|
else
|
||||||
throw new InvalidOperationException("invalid account id format");
|
throw new InvalidOperationException($"Invalid account id format, can't migrate account for server {server}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
public EFormat Format { get; private set; } = EFormat.Unencrypted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Depending on <see cref="Format"/>, this is either a Guid as string or a base64 encoded byte array.
|
||||||
|
/// </summary>
|
||||||
[JsonPropertyName("Id")]
|
[JsonPropertyName("Id")]
|
||||||
[JsonInclude]
|
[JsonInclude]
|
||||||
public string EncryptedId { get; private set; } = null!;
|
public string EncryptedId { get; private set; } = null!;
|
||||||
|
|
||||||
|
[JsonInclude]
|
||||||
|
public byte[]? Entropy { get; private set; }
|
||||||
|
|
||||||
public string Server { get; init; } = null!;
|
public string Server { get; init; } = null!;
|
||||||
|
|
||||||
[JsonIgnore] public bool IsUsable => DecryptAccountId(EncryptedId) != null;
|
[JsonIgnore] public bool IsUsable => DecryptAccountId() != null;
|
||||||
|
|
||||||
[JsonIgnore] public Guid AccountId => DecryptAccountId(EncryptedId) ?? throw new InvalidOperationException();
|
[JsonIgnore] public Guid AccountId => DecryptAccountId() ?? throw new InvalidOperationException("Account id can't be read");
|
||||||
|
|
||||||
public List<string> CachedRoles { get; set; } = new();
|
public List<string> CachedRoles { get; set; } = new();
|
||||||
|
|
||||||
private Guid? DecryptAccountId(string id)
|
private Guid? DecryptAccountId()
|
||||||
|
{
|
||||||
|
if (Format == EFormat.UseProtectedData)
|
||||||
{
|
{
|
||||||
if (Guid.TryParse(id, out Guid guid) && guid != Guid.Empty)
|
|
||||||
return guid;
|
|
||||||
|
|
||||||
if (!id.StartsWith("s:"))
|
|
||||||
throw new InvalidOperationException("invalid prefix");
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
byte[] guidBytes = ProtectedData.Unprotect(Convert.FromBase64String(id.Substring(2)),
|
byte[] guidBytes = ProtectedData.Unprotect(Convert.FromBase64String(EncryptedId), Entropy, DataProtectionScope.CurrentUser);
|
||||||
ConfigurationData.Entropy, DataProtectionScope.CurrentUser);
|
|
||||||
return new Guid(guidBytes);
|
return new Guid(guidBytes);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
PluginLog.Verbose(e, $"Could not load account id {id}");
|
PluginLog.Verbose(e, $"Could not load account id {EncryptedId}");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (Format == EFormat.Unencrypted)
|
||||||
|
return Guid.Parse(EncryptedId);
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private string EncryptAccountId(Guid g)
|
private (string encryptedId, byte[]? entropy, EFormat format) EncryptAccountId(Guid g)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
byte[] guidBytes = ProtectedData.Protect(g.ToByteArray(), ConfigurationData.Entropy,
|
byte[] entropy = RandomNumberGenerator.GetBytes(16);
|
||||||
DataProtectionScope.CurrentUser);
|
byte[] guidBytes = ProtectedData.Protect(g.ToByteArray(), entropy, DataProtectionScope.CurrentUser);
|
||||||
return $"s:{Convert.ToBase64String(guidBytes)}";
|
return (Convert.ToBase64String(guidBytes), entropy, EFormat.UseProtectedData);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
return g.ToString();
|
return (g.ToString(), null, EFormat.Unencrypted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool EncryptIfNeeded()
|
public bool EncryptIfNeeded()
|
||||||
{
|
{
|
||||||
if (Guid.TryParse(EncryptedId, out Guid g))
|
if (Format == EFormat.Unencrypted)
|
||||||
{
|
{
|
||||||
string oldId = EncryptedId;
|
var (newId, newEntropy, newFormat) = EncryptAccountId(Guid.Parse(EncryptedId));
|
||||||
EncryptedId = EncryptAccountId(g);
|
if (newFormat != EFormat.Unencrypted)
|
||||||
return oldId != EncryptedId;
|
{
|
||||||
|
EncryptedId = newId;
|
||||||
|
Entropy = newEntropy;
|
||||||
|
Format = newFormat;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
if (Format == EFormat.UseProtectedData && ConfigurationData.FixedV1Entropy.SequenceEqual(Entropy ?? Array.Empty<byte>()))
|
||||||
|
{
|
||||||
|
Guid? g = DecryptAccountId();
|
||||||
|
if (g != null)
|
||||||
|
{
|
||||||
|
(EncryptedId, Entropy, Format) = EncryptAccountId(g.Value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum EFormat
|
||||||
|
{
|
||||||
|
Unencrypted = 1,
|
||||||
|
UseProtectedData = 2,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
namespace Pal.Client.Configuration
|
using System;
|
||||||
|
|
||||||
|
namespace Pal.Client.Configuration
|
||||||
{
|
{
|
||||||
internal static class ConfigurationData
|
internal static class ConfigurationData
|
||||||
{
|
{
|
||||||
internal static readonly byte[] Entropy = { 0x22, 0x4b, 0xe7, 0x21, 0x44, 0x83, 0x69, 0x55, 0x80, 0x38 };
|
[Obsolete("for V1 import")]
|
||||||
|
internal static readonly byte[] FixedV1Entropy = { 0x22, 0x4b, 0xe7, 0x21, 0x44, 0x83, 0x69, 0x55, 0x80, 0x38 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,19 @@ namespace Pal.Client.Configuration
|
|||||||
|
|
||||||
public string ConfigPath => Path.Join(_pluginInterface.GetPluginConfigDirectory(), "palace-pal.config.json");
|
public string ConfigPath => Path.Join(_pluginInterface.GetPluginConfigDirectory(), "palace-pal.config.json");
|
||||||
|
|
||||||
|
public IPalacePalConfiguration Load()
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<ConfigurationV7>(File.ReadAllText(ConfigPath, Encoding.UTF8)) ??
|
||||||
|
new ConfigurationV7();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save(IConfigurationInConfigDirectory config)
|
||||||
|
{
|
||||||
|
File.WriteAllText(ConfigPath,
|
||||||
|
JsonSerializer.Serialize(config, config.GetType(), new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }),
|
||||||
|
Encoding.UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
#pragma warning disable CS0612
|
#pragma warning disable CS0612
|
||||||
#pragma warning disable CS0618
|
#pragma warning disable CS0618
|
||||||
public void Migrate()
|
public void Migrate()
|
||||||
@ -42,19 +55,6 @@ namespace Pal.Client.Configuration
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPalacePalConfiguration Load()
|
|
||||||
{
|
|
||||||
return JsonSerializer.Deserialize<ConfigurationV7>(File.ReadAllText(ConfigPath, Encoding.UTF8)) ??
|
|
||||||
new ConfigurationV7();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Save(IConfigurationInConfigDirectory config)
|
|
||||||
{
|
|
||||||
File.WriteAllText(ConfigPath,
|
|
||||||
JsonSerializer.Serialize(config, config.GetType(), new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }),
|
|
||||||
Encoding.UTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConfigurationV7 MigrateToV7(ConfigurationV1 v1)
|
private ConfigurationV7 MigrateToV7(ConfigurationV1 v1)
|
||||||
{
|
{
|
||||||
ConfigurationV7 v7 = new()
|
ConfigurationV7 v7 = new()
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
<AssemblyName>Palace Pal</AssemblyName>
|
<AssemblyName>Palace Pal</AssemblyName>
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user