using System;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Controllers;
using NBitcoin;
namespace BTCPayServer.Configuration
{
public class ExternalConnectionString
{
public ExternalConnectionString()
{
}
public ExternalConnectionString(Uri server)
{
Server = server;
}
public Uri Server { get; set; }
public byte[] Macaroon { get; set; }
public Macaroons Macaroons { get; set; }
public string MacaroonFilePath { get; set; }
public string CertificateThumbprint { get; set; }
public string MacaroonDirectoryPath { get; set; }
public string APIToken { get; set; }
public string CookieFilePath { get; set; }
public string AccessKey { get; set; }
///
/// Return a connectionString which does not depends on external resources or information like relative path or file path
///
///
public async Task Expand(Uri absoluteUrlBase, ExternalServiceTypes serviceType, ChainName network)
{
var connectionString = this.Clone();
// Transform relative URI into absolute URI
var serviceUri = connectionString.Server.IsAbsoluteUri ? connectionString.Server : ToRelative(absoluteUrlBase, connectionString.Server.ToString());
var isSecure = network != ChainName.Mainnet ||
serviceUri.Scheme == "https" ||
serviceUri.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase) ||
Extensions.IsLocalNetwork(serviceUri.DnsSafeHost);
if (!isSecure)
{
throw new System.Security.SecurityException($"Insecure transport protocol to access this service, please use HTTPS or TOR");
}
connectionString.Server = serviceUri;
if (serviceType == ExternalServiceTypes.LNDGRPC || serviceType == ExternalServiceTypes.LNDRest || serviceType == ExternalServiceTypes.CLightningRest)
{
// Read the MacaroonDirectory
if (connectionString.MacaroonDirectoryPath != null)
{
try
{
connectionString.Macaroons = await Macaroons.GetFromDirectoryAsync(connectionString.MacaroonDirectoryPath);
connectionString.MacaroonDirectoryPath = null;
}
catch (Exception ex)
{
throw new System.IO.DirectoryNotFoundException("Macaroon directory path not found", ex);
}
}
// Read the MacaroonFilePath
if (connectionString.MacaroonFilePath != null)
{
try
{
connectionString.Macaroon = await System.IO.File.ReadAllBytesAsync(connectionString.MacaroonFilePath);
connectionString.MacaroonFilePath = null;
}
catch (Exception ex)
{
throw new System.IO.FileNotFoundException("Macaroon not found", ex);
}
}
}
if (new[] { ExternalServiceTypes.Charge, ExternalServiceTypes.RTL, ExternalServiceTypes.ThunderHub,
ExternalServiceTypes.Spark, ExternalServiceTypes.Configurator, ExternalServiceTypes.Torq }.Contains(serviceType))
{
// Read access key from cookie file
if (connectionString.CookieFilePath != null)
{
bool isFake = connectionString.CookieFilePath == "fake"; // Hacks for testing
string cookieFileContent = isFake ? "fake" : null;
if (!isFake)
{
try
{
cookieFileContent = await System.IO.File.ReadAllTextAsync(connectionString.CookieFilePath);
}
catch (Exception ex)
{
throw new System.IO.FileNotFoundException("Cookie file path not found", ex);
}
}
connectionString.CookieFilePath = null;
if (serviceType == ExternalServiceTypes.RTL || serviceType == ExternalServiceTypes.Configurator ||
serviceType == ExternalServiceTypes.ThunderHub || serviceType == ExternalServiceTypes.Torq)
{
connectionString.AccessKey = cookieFileContent;
}
else if (serviceType == ExternalServiceTypes.Spark)
{
var cookie = (isFake ? "fake:fake:fake" : cookieFileContent).Split(':');
if (cookie.Length >= 3)
{
connectionString.AccessKey = cookie[2];
}
else
{
throw new FormatException("Invalid cookiefile format");
}
}
else if (serviceType == ExternalServiceTypes.Charge)
{
connectionString.APIToken = isFake ? "fake" : cookieFileContent;
}
}
}
return connectionString;
}
private Uri ToRelative(Uri absoluteUrlBase, string path)
{
if (path.StartsWith('/'))
path = path.Substring(1);
return new Uri($"{absoluteUrlBase.AbsoluteUri.WithTrailingSlash()}{path}", UriKind.Absolute);
}
public ExternalConnectionString Clone()
{
return new ExternalConnectionString()
{
MacaroonFilePath = MacaroonFilePath,
CertificateThumbprint = CertificateThumbprint,
Macaroon = Macaroon,
MacaroonDirectoryPath = MacaroonDirectoryPath,
Server = Server,
APIToken = APIToken,
CookieFilePath = CookieFilePath,
AccessKey = AccessKey,
Macaroons = Macaroons?.Clone()
};
}
public bool? IsOnion()
{
return Server?.IsOnion();
}
public static bool TryParse(string str, out ExternalConnectionString result, out string error)
{
ArgumentNullException.ThrowIfNull(str);
error = null;
result = null;
var resultTemp = new ExternalConnectionString();
foreach (var kv in str.Split(';')
.Select(part => part.Split('='))
.Where(kv => kv.Length == 2))
{
switch (kv[0].ToLowerInvariant())
{
case "server":
if (resultTemp.Server != null)
{
error = "Duplicated server attribute";
return false;
}
if (!Uri.IsWellFormedUriString(kv[1], UriKind.RelativeOrAbsolute))
{
error = "Invalid URI";
return false;
}
resultTemp.Server = new Uri(kv[1], UriKind.RelativeOrAbsolute);
if (!resultTemp.Server.IsAbsoluteUri && (kv[1].Length == 0 || kv[1][0] != '/'))
resultTemp.Server = new Uri($"/{kv[1]}", UriKind.RelativeOrAbsolute);
break;
case "cookiefile":
case "cookiefilepath":
if (resultTemp.CookieFilePath != null)
{
error = "Duplicated cookiefile attribute";
return false;
}
resultTemp.CookieFilePath = kv[1];
break;
case "macaroondirectorypath":
resultTemp.MacaroonDirectoryPath = kv[1];
break;
case "certthumbprint":
resultTemp.CertificateThumbprint = kv[1];
break;
case "macaroonfilepath":
resultTemp.MacaroonFilePath = kv[1];
break;
case "api-token":
resultTemp.APIToken = kv[1];
break;
case "access-key":
resultTemp.AccessKey = kv[1];
break;
}
}
result = resultTemp;
return true;
}
}
}