mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-05-24 08:25:30 -04:00
Merge pull request #1708 from rmcrackan/rmcrackan/1704-mkb79import
fix bug with importing mkb79 auth with website_cookies: null vs empty
This commit is contained in:
@@ -7,6 +7,7 @@ using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AudibleUtilities;
|
||||
@@ -53,7 +54,9 @@ public partial class Mkb79Auth : IIdentityMaintainer
|
||||
public Dictionary<string, string?>? WebsiteCookies
|
||||
{
|
||||
get => _websiteCookies?.ToObject<Dictionary<string, string?>>();
|
||||
private set => _websiteCookies = JObject.Parse(JsonConvert.SerializeObject(value, Converter.Settings));
|
||||
private set => _websiteCookies = value is null || value.Count == 0
|
||||
? null
|
||||
: JObject.Parse(JsonConvert.SerializeObject(value, Converter.Settings));
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
@@ -123,7 +126,75 @@ public partial class Mkb79Auth
|
||||
=> JsonConvert.DeserializeObject<Mkb79Auth>(json, Converter.Settings);
|
||||
|
||||
public string ToJson()
|
||||
=> JObject.Parse(JsonConvert.SerializeObject(this, Converter.Settings)).ToString(Formatting.Indented);
|
||||
{
|
||||
var jo = JObject.Parse(JsonConvert.SerializeObject(this, Converter.Settings));
|
||||
ApplyAudibleCliExportConventions(jo);
|
||||
return jo.ToString(Formatting.Indented);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// audible-cli expects <c>website_cookies</c> as JSON null when empty (not <c>{}</c>) and a PEM
|
||||
/// <c>device_private_key</c> with standard 64-character base64 lines and newline separators.
|
||||
/// </summary>
|
||||
internal static void ApplyAudibleCliExportConventions(JObject jo)
|
||||
{
|
||||
if (jo["website_cookies"] is JObject wc && !wc.Properties().Any())
|
||||
jo["website_cookies"] = JValue.CreateNull();
|
||||
|
||||
if (jo["device_private_key"]?.Type == JTokenType.String)
|
||||
{
|
||||
var s = jo["device_private_key"]!.Value<string>();
|
||||
var formatted = FormatDevicePrivateKeyForAudibleCliExport(s);
|
||||
if (formatted is not null)
|
||||
jo["device_private_key"] = formatted;
|
||||
}
|
||||
}
|
||||
|
||||
private static string? FormatDevicePrivateKeyForAudibleCliExport(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return value;
|
||||
|
||||
var trimmed = value.Trim();
|
||||
string payload;
|
||||
if (trimmed.StartsWith(PrivateKey.REQUIRED_BEGINNING, StringComparison.Ordinal))
|
||||
{
|
||||
var endIdx = trimmed.LastIndexOf(PrivateKey.REQUIRED_ENDING, StringComparison.Ordinal);
|
||||
if (endIdx < PrivateKey.REQUIRED_BEGINNING.Length)
|
||||
return value;
|
||||
|
||||
payload = trimmed
|
||||
.Substring(PrivateKey.REQUIRED_BEGINNING.Length, endIdx - PrivateKey.REQUIRED_BEGINNING.Length)
|
||||
.Replace("\r", "")
|
||||
.Replace("\n", "")
|
||||
.Replace("\\n", "", StringComparison.Ordinal)
|
||||
.Trim();
|
||||
}
|
||||
else
|
||||
payload = trimmed;
|
||||
|
||||
if (payload.Length == 0)
|
||||
return value;
|
||||
|
||||
try
|
||||
{
|
||||
Convert.FromBase64String(payload);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(PrivateKey.REQUIRED_BEGINNING).Append('\n');
|
||||
for (var i = 0; i < payload.Length; i += 64)
|
||||
{
|
||||
var len = Math.Min(64, payload.Length - i);
|
||||
sb.Append(payload, i, len).Append('\n');
|
||||
}
|
||||
sb.Append(PrivateKey.REQUIRED_ENDING).Append('\n');
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public async Task<Account> ToAccountAsync()
|
||||
{
|
||||
@@ -196,8 +267,7 @@ public partial class Mkb79Auth
|
||||
|
||||
public static class Serialize
|
||||
{
|
||||
public static string ToJson(this Mkb79Auth self)
|
||||
=> JObject.Parse(JsonConvert.SerializeObject(self, Converter.Settings)).ToString(Formatting.Indented);
|
||||
public static string ToJson(this Mkb79Auth self) => self.ToJson();
|
||||
}
|
||||
|
||||
internal static class Converter
|
||||
|
||||
68
Source/_Tests/AudibleUtilities.Tests/Mkb79AuthExportTests.cs
Normal file
68
Source/_Tests/AudibleUtilities.Tests/Mkb79AuthExportTests.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using AssertionHelper;
|
||||
using AudibleApi.Cryptography;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace AudibleUtilities.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class Mkb79AuthExportTests
|
||||
{
|
||||
static string MinimalMkb79Json(Action<JObject>? tweak = null)
|
||||
{
|
||||
var jo = new JObject
|
||||
{
|
||||
["website_cookies"] = new JObject(),
|
||||
["adp_token"] = "a",
|
||||
["access_token"] = "b",
|
||||
["refresh_token"] = "c",
|
||||
["device_private_key"] = "d",
|
||||
["store_authentication_cookie"] = new JObject { ["cookie"] = "" },
|
||||
["device_info"] = new JObject(),
|
||||
["customer_info"] = new JObject(),
|
||||
["expires"] = 0,
|
||||
["locale_code"] = "us",
|
||||
["with_username"] = false,
|
||||
};
|
||||
tweak?.Invoke(jo);
|
||||
return jo.ToString(Newtonsoft.Json.Formatting.None);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ToJson_empty_website_cookies_is_null_not_object()
|
||||
{
|
||||
var auth = Mkb79Auth.FromJson(MinimalMkb79Json());
|
||||
auth.BeNotNull();
|
||||
var jo = JObject.Parse(auth.ToJson());
|
||||
Assert.AreEqual(JTokenType.Null, jo["website_cookies"]!.Type);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ToJson_device_private_key_is_pem_with_64_char_lines()
|
||||
{
|
||||
var keyMaterial = Convert.ToBase64String(new byte[100]);
|
||||
var singleLine = PrivateKey.REQUIRED_BEGINNING + keyMaterial + PrivateKey.REQUIRED_ENDING;
|
||||
var auth = Mkb79Auth.FromJson(MinimalMkb79Json(j =>
|
||||
{
|
||||
j["website_cookies"] = JValue.CreateNull();
|
||||
j["device_private_key"] = singleLine;
|
||||
}));
|
||||
auth.BeNotNull();
|
||||
var pem = JObject.Parse(auth.ToJson())["device_private_key"]!.Value<string>()!;
|
||||
var lines = pem.Replace("\r\n", "\n").Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
lines[0].Should().Be(PrivateKey.REQUIRED_BEGINNING);
|
||||
lines[^1].Should().Be(PrivateKey.REQUIRED_ENDING);
|
||||
foreach (var body in lines.Skip(1).Take(lines.Length - 2))
|
||||
Assert.IsTrue(body.Length <= 64);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Serialize_ToJson_matches_instance_ToJson()
|
||||
{
|
||||
var auth = Mkb79Auth.FromJson(MinimalMkb79Json(j => j["device_private_key"] = "AAAA"));
|
||||
auth.BeNotNull();
|
||||
auth.ToJson().Should().Be(Serialize.ToJson(auth));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user