PalacePal/Pal.Client/Configuration/AccountConfigurationV7.cs

137 lines
4.6 KiB
C#
Raw Normal View History

2023-02-15 01:38:04 +00:00
using System;
using System.Collections.Generic;
2023-02-15 12:00:00 +00:00
using System.Linq;
2023-02-15 01:38:04 +00:00
using System.Security.Cryptography;
using System.Text.Json.Serialization;
using Dalamud.Logging;
namespace Pal.Client.Configuration
{
public class AccountConfigurationV7 : IAccountConfiguration
{
[JsonConstructor]
public AccountConfigurationV7()
{
}
public AccountConfigurationV7(string server, Guid accountId)
{
Server = server;
2023-02-15 12:00:00 +00:00
(EncryptedId, Entropy, Format) = EncryptAccountId(accountId);
2023-02-15 01:38:04 +00:00
}
[Obsolete("for V1 import")]
public AccountConfigurationV7(string server, string accountId)
{
Server = server;
if (accountId.StartsWith("s:"))
2023-02-15 12:00:00 +00:00
{
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);
}
2023-02-15 01:38:04 +00:00
else if (Guid.TryParse(accountId, out Guid guid))
2023-02-15 12:00:00 +00:00
(EncryptedId, Entropy, Format) = EncryptAccountId(guid);
2023-02-15 01:38:04 +00:00
else
2023-02-15 12:00:00 +00:00
throw new InvalidOperationException($"Invalid account id format, can't migrate account for server {server}");
2023-02-15 01:38:04 +00:00
}
2023-02-15 12:00:00 +00:00
[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")]
[JsonInclude]
public string EncryptedId { get; private set; } = null!;
2023-02-15 01:38:04 +00:00
2023-02-15 12:00:00 +00:00
[JsonInclude]
public byte[]? Entropy { get; private set; }
public string Server { get; init; } = null!;
2023-02-15 01:38:04 +00:00
2023-02-15 12:00:00 +00:00
[JsonIgnore] public bool IsUsable => DecryptAccountId() != null;
2023-02-15 01:38:04 +00:00
2023-02-15 12:00:00 +00:00
[JsonIgnore] public Guid AccountId => DecryptAccountId() ?? throw new InvalidOperationException("Account id can't be read");
2023-02-15 01:38:04 +00:00
public List<string> CachedRoles { get; set; } = new();
2023-02-15 12:00:00 +00:00
private Guid? DecryptAccountId()
2023-02-15 01:38:04 +00:00
{
2023-02-15 12:00:00 +00:00
if (Format == EFormat.UseProtectedData)
2023-02-15 01:38:04 +00:00
{
2023-02-15 12:00:00 +00:00
try
{
byte[] guidBytes = ProtectedData.Unprotect(Convert.FromBase64String(EncryptedId), Entropy, DataProtectionScope.CurrentUser);
return new Guid(guidBytes);
}
catch (Exception e)
{
PluginLog.Verbose(e, $"Could not load account id {EncryptedId}");
return null;
}
2023-02-15 01:38:04 +00:00
}
2023-02-15 12:00:00 +00:00
else if (Format == EFormat.Unencrypted)
return Guid.Parse(EncryptedId);
else
2023-02-15 01:38:04 +00:00
return null;
}
2023-02-15 12:00:00 +00:00
private (string encryptedId, byte[]? entropy, EFormat format) EncryptAccountId(Guid g)
2023-02-15 01:38:04 +00:00
{
try
{
2023-02-15 12:00:00 +00:00
byte[] entropy = RandomNumberGenerator.GetBytes(16);
byte[] guidBytes = ProtectedData.Protect(g.ToByteArray(), entropy, DataProtectionScope.CurrentUser);
return (Convert.ToBase64String(guidBytes), entropy, EFormat.UseProtectedData);
2023-02-15 01:38:04 +00:00
}
catch (Exception)
2023-02-15 01:38:04 +00:00
{
2023-02-15 12:00:00 +00:00
return (g.ToString(), null, EFormat.Unencrypted);
2023-02-15 01:38:04 +00:00
}
}
2023-02-15 12:00:00 +00:00
public bool EncryptIfNeeded()
{
2023-02-15 12:00:00 +00:00
if (Format == EFormat.Unencrypted)
{
var (newId, newEntropy, newFormat) = EncryptAccountId(Guid.Parse(EncryptedId));
if (newFormat != EFormat.Unencrypted)
{
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>()))
{
2023-02-15 12:00:00 +00:00
Guid? g = DecryptAccountId();
if (g != null)
{
(EncryptedId, Entropy, Format) = EncryptAccountId(g.Value);
return true;
}
}
2023-02-15 12:00:00 +00:00
#pragma warning restore CS0618 // Type or member is obsolete
return false;
}
2023-02-15 12:00:00 +00:00
public enum EFormat
{
Unencrypted = 1,
UseProtectedData = 2,
}
2023-02-15 01:38:04 +00:00
}
}