.NET 2.0sify, use the same configuration framework as NBXplorer

This commit is contained in:
NicolasDorier 2017-09-23 01:31:29 +09:00
parent 99ae549c24
commit ce055dece9
16 changed files with 465 additions and 584 deletions

View file

@ -77,10 +77,10 @@ namespace BTCPayServer.Tests
ServerUri = new Uri("http://127.0.0.1:" + port + "/");
BTCPayServerOptions options = new BTCPayServerOptions();
options.LoadArgs(new TextFileConfiguration(new string[] { "-datadir", _Directory }));
var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory });
_Host = new WebHostBuilder()
.UseConfiguration(conf)
.ConfigureServices(s =>
{
s.AddSingleton<IRateProvider>(new MockRateProvider(new Rate("USD", 5000m)));
@ -91,7 +91,6 @@ namespace BTCPayServer.Tests
.AddProvider(Logs.LogProvider);
});
})
.AddPayServer(options)
.UseKestrel()
.UseStartup<Startup>()
.Build();

View file

@ -71,7 +71,7 @@ namespace BTCPayServer.Tests
config.AppendLine($"rpc.auth={Node.AuthenticationString}");
config.AppendLine($"node.endpoint={Node.NodeEndpoint.Address}:{Node.NodeEndpoint.Port}");
File.WriteAllText(Path.Combine(launcher2.CurrentDirectory, "settings.config"), config.ToString());
_Process = launcher.Start("dotnet", $"NBXplorer.dll -datadir \"{launcher2.CurrentDirectory}\"");
_Process = launcher.Start("dotnet", $"NBXplorer.dll --datadir \"{launcher2.CurrentDirectory}\"");
ExplorerClient = new NBXplorer.ExplorerClient(Node.Network, new Uri($"http://127.0.0.1:{port}/"));
CookieFile = Path.Combine(launcher2.CurrentDirectory, ".cookie");
File.Create(CookieFile).Close(); //Will be wipedout when the client starts

View file

@ -15,6 +15,9 @@
<PackageReference Include="NBitpayClient" Version="1.0.0.6" />
<PackageReference Include="DBreeze" Version="1.87.0" />
<PackageReference Include="NBXplorer.Client" Version="1.0.0.12" />
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.1" />
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.1" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.9" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
<PackageReference Include="System.Xml.XmlSerializer" Version="4.0.11" />
</ItemGroup>

View file

@ -7,6 +7,8 @@ using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using StandardConfiguration;
using Microsoft.Extensions.Configuration;
namespace BTCPayServer.Configuration
{
@ -41,75 +43,20 @@ namespace BTCPayServer.Configuration
set;
}
public void LoadArgs(TextFileConfiguration consoleConfig)
public void LoadArgs(IConfiguration conf)
{
ConfigurationFile = consoleConfig.GetOrDefault<string>("conf", null);
DataDir = consoleConfig.GetOrDefault<string>("datadir", null);
if(DataDir != null && ConfigurationFile != null)
{
var isRelativePath = Path.GetFullPath(ConfigurationFile).Length > ConfigurationFile.Length;
if(isRelativePath)
{
ConfigurationFile = Path.Combine(DataDir, ConfigurationFile);
}
}
Network = consoleConfig.GetOrDefault<bool>("testnet", false) ? Network.TestNet :
consoleConfig.GetOrDefault<bool>("regtest", false) ? Network.RegTest :
null;
if(DataDir != null && ConfigurationFile == null)
{
ConfigurationFile = GetDefaultConfigurationFile(Network != null);
}
if(ConfigurationFile != null)
{
AssetConfigFileExists();
var configTemp = TextFileConfiguration.Parse(File.ReadAllText(ConfigurationFile));
Network = Network ?? (configTemp.GetOrDefault<bool>("testnet", false) ? Network.TestNet :
configTemp.GetOrDefault<bool>("regtest", false) ? Network.RegTest :
null);
}
Network = Network ?? Network.Main;
if(DataDir == null)
{
DataDir = DefaultDataDirectory.GetDefaultDirectory("BTCPayServer", Network, true);
ConfigurationFile = GetDefaultConfigurationFile(true);
}
if(!Directory.Exists(DataDir))
throw new ConfigurationException("Data directory does not exists");
var config = TextFileConfiguration.Parse(File.ReadAllText(ConfigurationFile));
consoleConfig.MergeInto(config, true);
var networkInfo = DefaultConfiguration.GetNetwork(conf);
Network = networkInfo?.Network;
if(Network == null)
throw new ConfigException("Invalid network");
DataDir = conf.GetOrDefault<string>("datadir", networkInfo.DefaultDataDirectory);
Logs.Configuration.LogInformation("Network: " + Network);
Logs.Configuration.LogInformation("Data directory set to " + DataDir);
Logs.Configuration.LogInformation("Configuration file set to " + ConfigurationFile);
var defaultPort = config.GetOrDefault<int>("port", GetDefaultPort(Network));
Listen = config
.GetAll("bind")
.Select(p => ConvertToEndpoint(p, defaultPort))
.ToList();
if(Listen.Count == 0)
{
Listen.Add(new IPEndPoint(IPAddress.Parse("127.0.0.1"), defaultPort));
}
Explorer = config.GetOrDefault<Uri>("explorer.url", GetDefaultNXplorerUri());
CookieFile = config.GetOrDefault<string>("explorer.cookiefile", GetExplorerDefaultCookiePath());
ExternalUrl = config.GetOrDefault<Uri>("externalurl", null);
if(ExternalUrl == null)
{
var ip = Listen.Where(u => !u.Address.ToString().Equals("0.0.0.0", StringComparison.OrdinalIgnoreCase)).FirstOrDefault()
?? new IPEndPoint(IPAddress.Parse("127.0.0.1"), defaultPort);
ExternalUrl = new Uri($"http://{ip.Address}:{ip.Port}/");
}
RequireHttps = config.GetOrDefault<bool>("requirehttps", false);
Explorer = conf.GetOrDefault<Uri>("explorer.url", networkInfo.DefaultExplorerUrl);
CookieFile = conf.GetOrDefault<string>("explorer.cookiefile", networkInfo.DefaultExplorerCookieFile);
ExternalUrl = conf.GetOrDefault<Uri>("externalurl", null);
RequireHttps = conf.GetOrDefault<bool>("requirehttps", false);
}
public bool RequireHttps
@ -121,92 +68,5 @@ namespace BTCPayServer.Configuration
{
get; set;
}
private Uri GetDefaultNXplorerUri()
{
return new Uri("http://localhost:" + GetNXplorerDefaultPort(Network));
}
public string[] GetUrls()
{
return Listen.Select(b => "http://" + b + "/").ToArray();
}
private void AssetConfigFileExists()
{
if(!File.Exists(ConfigurationFile))
throw new ConfigurationException("Configuration file does not exists");
}
public static IPEndPoint ConvertToEndpoint(string str, int defaultPort)
{
var portOut = defaultPort;
var hostOut = "";
int colon = str.LastIndexOf(':');
// if a : is found, and it either follows a [...], or no other : is in the string, treat it as port separator
bool fHaveColon = colon != -1;
bool fBracketed = fHaveColon && (str[0] == '[' && str[colon - 1] == ']'); // if there is a colon, and in[0]=='[', colon is not 0, so in[colon-1] is safe
bool fMultiColon = fHaveColon && (str.LastIndexOf(':', colon - 1) != -1);
if(fHaveColon && (colon == 0 || fBracketed || !fMultiColon))
{
int n;
if(int.TryParse(str.Substring(colon + 1), out n) && n > 0 && n < 0x10000)
{
str = str.Substring(0, colon);
portOut = n;
}
}
if(str.Length > 0 && str[0] == '[' && str[str.Length - 1] == ']')
hostOut = str.Substring(1, str.Length - 2);
else
hostOut = str;
return new IPEndPoint(IPAddress.Parse(hostOut), portOut);
}
const string DefaultConfigFile = "settings.config";
private string GetDefaultConfigurationFile(bool createIfNotExist)
{
var config = Path.Combine(DataDir, DefaultConfigFile);
Logs.Configuration.LogInformation("Configuration file set to " + config);
if(createIfNotExist && !File.Exists(config))
{
Logs.Configuration.LogInformation("Creating configuration file");
StringBuilder builder = new StringBuilder();
builder.AppendLine("### Global settings ###");
builder.AppendLine("#testnet=0");
builder.AppendLine("#regtest=0");
builder.AppendLine("#Put here the xpub key of your hardware wallet");
builder.AppendLine("#hdpubkey=xpub...");
builder.AppendLine();
builder.AppendLine("### Server settings ###");
builder.AppendLine("#port=" + GetDefaultPort(Network));
builder.AppendLine("#bind=127.0.0.1");
builder.AppendLine("#externalurl=http://127.0.0.1/");
builder.AppendLine();
builder.AppendLine("### NBXplorer settings ###");
builder.AppendLine("#explorer.url=" + GetDefaultNXplorerUri());
builder.AppendLine("#explorer.cookiefile=" + GetExplorerDefaultCookiePath());
File.WriteAllText(config, builder.ToString());
}
return config;
}
private string GetExplorerDefaultCookiePath()
{
return Path.Combine(DefaultDataDirectory.GetDefaultDirectory("NBXplorer", Network, false), ".cookie");
}
private int GetNXplorerDefaultPort(Network network)
{
return network == Network.Main ? 24444 :
network == Network.TestNet ? 24445 : 24446;
}
private int GetDefaultPort(Network network)
{
return network == Network.Main ? 23000 :
network == Network.TestNet ? 23001 : 23002;
}
}
}

View file

@ -47,7 +47,7 @@ namespace BTCPayServer.Configuration
}
catch(Exception ex)
{
throw new ConfigurationException($"Could not connect to NBXplorer, {ex.Message}");
throw new ConfigException($"Could not connect to NBXplorer, {ex.Message}");
}
DBreezeEngine db = new DBreezeEngine(CreateDBPath(opts, "TokensDB"));
_Resources.Add(db);

View file

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Configuration
{
public class ConfigException : Exception
{
public ConfigException(string message) : base(message)
{
}
}
}

View file

@ -0,0 +1,52 @@
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
namespace BTCPayServer.Configuration
{
public static class ConfigurationExtensions
{
public static T GetOrDefault<T>(this IConfiguration configuration, string key, T defaultValue)
{
var str = configuration[key] ?? configuration[key.Replace(".", string.Empty)];
if(str == null)
return defaultValue;
if(typeof(T) == typeof(bool))
{
var trueValues = new[] { "1", "true" };
var falseValues = new[] { "0", "false" };
if(trueValues.Contains(str, StringComparer.OrdinalIgnoreCase))
return (T)(object)true;
if(falseValues.Contains(str, StringComparer.OrdinalIgnoreCase))
return (T)(object)false;
throw new FormatException();
}
else if(typeof(T) == typeof(Uri))
return (T)(object)new Uri(str, UriKind.Absolute);
else if(typeof(T) == typeof(string))
return (T)(object)str;
else if(typeof(T) == typeof(IPEndPoint))
{
var separator = str.LastIndexOf(":");
if(separator == -1)
throw new FormatException();
var ip = str.Substring(0, separator);
var port = str.Substring(separator + 1);
return (T)(object)new IPEndPoint(IPAddress.Parse(ip), int.Parse(port));
}
else if(typeof(T) == typeof(int))
{
return (T)(object)int.Parse(str, CultureInfo.InvariantCulture);
}
else
{
throw new NotSupportedException("Configuration value does not support time " + typeof(T).Name);
}
}
}
}

View file

@ -0,0 +1,96 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Threading.Tasks;
using NBitcoin;
using System.Text;
using CommandLine;
namespace BTCPayServer.Configuration
{
public class DefaultConfiguration : StandardConfiguration.DefaultConfiguration
{
protected override CommandLineApplication CreateCommandLineApplicationCore()
{
CommandLineApplication app = new CommandLineApplication(true)
{
FullName = "NBXplorer\r\nLightweight block explorer for tracking HD wallets",
Name = "NBXplorer"
};
app.HelpOption("-? | -h | --help");
app.Option("-n | --network", $"Set the network among ({NetworkInformation.ToStringAll()}) (default: {Network.Main.ToString()})", CommandOptionType.SingleValue);
app.Option("--testnet | -testnet", $"Use testnet", CommandOptionType.BoolValue);
app.Option("--regtest | -regtest", $"Use regtest", CommandOptionType.BoolValue);
app.Option("--requirehttps", $"Will redirect to https version of the website (default: false)", CommandOptionType.BoolValue);
app.Option("--externalurl", $"The external url of the website", CommandOptionType.SingleValue);
app.Option("--explorerurl", $"Url of the NBxplorer (default: : Default setting of NBXplorer for the network)", CommandOptionType.SingleValue);
app.Option("--explorercookiefile", $"Path to the cookie file (default: Default setting of NBXplorer for the network)", CommandOptionType.SingleValue);
return app;
}
protected override string GetDefaultDataDir(IConfiguration conf)
{
return GetNetwork(conf).DefaultDataDirectory;
}
protected override string GetDefaultConfigurationFile(IConfiguration conf)
{
var network = GetNetwork(conf);
var dataDir = conf["datadir"];
if(dataDir == null)
return network.DefaultConfigurationFile;
var fileName = Path.GetFileName(network.DefaultConfigurationFile);
return Path.Combine(dataDir, fileName);
}
public static NetworkInformation GetNetwork(IConfiguration conf)
{
var network = conf.GetOrDefault<string>("network", null);
if(network != null)
{
var info = NetworkInformation.GetNetworkByName(network);
if(info == null)
throw new ConfigException($"Invalid network name {network}");
return info;
}
var net = conf.GetOrDefault<bool>("regtest", false) ? Network.RegTest :
conf.GetOrDefault<bool>("testnet", false) ? Network.TestNet : Network.Main;
return NetworkInformation.GetNetworkByName(net.Name);
}
protected override string GetDefaultConfigurationFileTemplate(IConfiguration conf)
{
var network = GetNetwork(conf);
StringBuilder builder = new StringBuilder();
builder.AppendLine("### Global settings ###");
builder.AppendLine("#testnet=0");
builder.AppendLine("#regtest=0");
builder.AppendLine();
builder.AppendLine("### Server settings ###");
builder.AppendLine("#requirehttps=0");
builder.AppendLine("#port=" + network.DefaultPort);
builder.AppendLine("#bind=127.0.0.1");
builder.AppendLine("#externalurl=http://127.0.0.1/");
builder.AppendLine();
builder.AppendLine("### NBXplorer settings ###");
builder.AppendLine("#explorer.url=" + network.DefaultExplorerUrl.AbsoluteUri);
builder.AppendLine("#explorer.cookiefile=" + network.DefaultExplorerCookieFile);
return builder.ToString();
}
protected override IPEndPoint GetDefaultEndpoint(IConfiguration conf)
{
return new IPEndPoint(IPAddress.Parse("127.0.0.1"), GetNetwork(conf).DefaultPort);
}
}
}

View file

@ -1,52 +0,0 @@
using BTCPayServer.Logging;
using Microsoft.Extensions.Logging;
using NBitcoin;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace BTCPayServer.Configuration
{
public class DefaultDataDirectory
{
public static string GetDefaultDirectory(string appName, Network network, bool createDirectory)
{
string directory = null;
var home = Environment.GetEnvironmentVariable("HOME");
if(!string.IsNullOrEmpty(home))
{
if(createDirectory)
Logs.Configuration.LogInformation("Using HOME environment variable for initializing application data");
directory = home;
directory = Path.Combine(directory, "." + appName.ToLowerInvariant());
}
else
{
var localAppData = Environment.GetEnvironmentVariable("APPDATA");
if(!string.IsNullOrEmpty(localAppData))
{
if(createDirectory)
Logs.Configuration.LogInformation("Using APPDATA environment variable for initializing application data");
directory = localAppData;
directory = Path.Combine(directory, appName);
}
else
{
throw new DirectoryNotFoundException("Could not find suitable datadir");
}
}
if(!Directory.Exists(directory) && createDirectory)
{
Directory.CreateDirectory(directory);
}
directory = Path.Combine(directory, network.Name);
if(!Directory.Exists(directory) && createDirectory)
{
Logs.Configuration.LogInformation("Creating data directory");
Directory.CreateDirectory(directory);
}
return directory;
}
}
}

View file

@ -0,0 +1,103 @@
using Microsoft.Extensions.Configuration;
using NBitcoin;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Configuration
{
public class NetworkInformation
{
static NetworkInformation()
{
_Networks = new Dictionary<string, NetworkInformation>();
foreach(var network in Network.GetNetworks())
{
NetworkInformation info = new NetworkInformation();
info.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", network.Name);
info.DefaultConfigurationFile = Path.Combine(info.DefaultDataDirectory, "settings.config");
info.DefaultExplorerCookieFile = Path.Combine(StandardConfiguration.DefaultDataDirectory.GetDirectory("NBXplorer", network.Name, false), ".cookie");
info.Network = network;
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24446", UriKind.Absolute);
info.DefaultPort = 23002;
_Networks.Add(network.Name, info);
if(network == Network.Main)
{
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24444", UriKind.Absolute);
Main = info;
info.DefaultPort = 23000;
}
if(network == Network.TestNet)
{
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24445", UriKind.Absolute);
info.DefaultPort = 23001;
}
}
}
static Dictionary<string, NetworkInformation> _Networks;
public static NetworkInformation GetNetworkByName(string name)
{
var value = _Networks.TryGet(name);
if(value != null)
return value;
//Maybe alias ?
var network = Network.GetNetwork(name);
if(network != null)
{
value = _Networks.TryGet(network.Name);
if(value != null)
return value;
}
return null;
}
public static NetworkInformation Main
{
get;
set;
}
public Network Network
{
get; set;
}
public string DefaultConfigurationFile
{
get;
set;
}
public string DefaultDataDirectory
{
get;
set;
}
public Uri DefaultExplorerUrl
{
get;
internal set;
}
public int DefaultPort
{
get;
private set;
}
public string DefaultExplorerCookieFile
{
get;
internal set;
}
public override string ToString()
{
return Network.ToString();
}
public static string ToStringAll()
{
return string.Join(", ", _Networks.Select(n => n.Key).ToArray());
}
}
}

View file

@ -1,221 +0,0 @@
using NBitcoin;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace BTCPayServer
{
public class ConfigurationException : Exception
{
public ConfigurationException(string message) : base(message)
{
}
}
public class TextFileConfiguration
{
private Dictionary<string, List<string>> _Args;
public TextFileConfiguration(string[] args)
{
_Args = new Dictionary<string, List<string>>();
string noValueParam = null;
Action flushNoValueParam = () =>
{
if(noValueParam != null)
{
Add(noValueParam, "1", false);
noValueParam = null;
}
};
foreach(var arg in args)
{
bool isParamName = arg.StartsWith("-", StringComparison.Ordinal);
if(isParamName)
{
var splitted = arg.Split('=');
if(splitted.Length > 1)
{
var value = String.Join("=", splitted.Skip(1).ToArray());
flushNoValueParam();
Add(splitted[0], value, false);
}
else
{
flushNoValueParam();
noValueParam = splitted[0];
}
}
else
{
if(noValueParam != null)
{
Add(noValueParam, arg, false);
noValueParam = null;
}
}
}
flushNoValueParam();
}
private void Add(string key, string value, bool sourcePriority)
{
key = NormalizeKey(key);
List<string> list;
if(!_Args.TryGetValue(key, out list))
{
list = new List<string>();
_Args.Add(key, list);
}
if(sourcePriority)
list.Insert(0, value);
else
list.Add(value);
}
private static string NormalizeKey(string key)
{
key = key.ToLowerInvariant();
while(key.Length > 0 && key[0] == '-')
{
key = key.Substring(1);
}
key = key.Replace(".", "");
return key;
}
public void MergeInto(TextFileConfiguration destination, bool sourcePriority)
{
foreach(var kv in _Args)
{
foreach(var v in kv.Value)
destination.Add(kv.Key, v, sourcePriority);
}
}
public TextFileConfiguration(Dictionary<string, List<string>> args)
{
_Args = args;
}
public static TextFileConfiguration Parse(string data)
{
Dictionary<string, List<string>> result = new Dictionary<string, List<string>>();
var lines = data.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
int lineCount = -1;
foreach(var l in lines)
{
lineCount++;
var line = l.Trim();
if(line.StartsWith("#", StringComparison.Ordinal))
continue;
var split = line.Split('=');
if(split.Length == 0)
continue;
if(split.Length == 1)
throw new FormatException("Line " + lineCount + ": No value are set");
var key = split[0];
key = NormalizeKey(key);
List<string> values;
if(!result.TryGetValue(key, out values))
{
values = new List<string>();
result.Add(key, values);
}
var value = String.Join("=", split.Skip(1).ToArray());
values.Add(value);
}
return new TextFileConfiguration(result);
}
public bool Contains(string key)
{
List<string> values;
return _Args.TryGetValue(key, out values);
}
public string[] GetAll(string key)
{
List<string> values;
if(!_Args.TryGetValue(key, out values))
return new string[0];
return values.ToArray();
}
private List<Tuple<string, string>> _Aliases = new List<Tuple<string, string>>();
public void AddAlias(string from, string to)
{
from = NormalizeKey(from);
to = NormalizeKey(to);
_Aliases.Add(Tuple.Create(from, to));
}
public T GetOrDefault<T>(string key, T defaultValue)
{
key = NormalizeKey(key);
var aliases = _Aliases
.Where(a => a.Item1 == key || a.Item2 == key)
.Select(a => a.Item1 == key ? a.Item2 : a.Item1)
.ToList();
aliases.Insert(0, key);
foreach(var alias in aliases)
{
List<string> values;
if(!_Args.TryGetValue(alias, out values))
continue;
if(values.Count == 0)
continue;
try
{
return ConvertValue<T>(values[0]);
}
catch(FormatException) { throw new ConfigurationException("Key " + key + " should be of type " + typeof(T).Name); }
}
return defaultValue;
}
private T ConvertValue<T>(string str)
{
if(typeof(T) == typeof(bool))
{
var trueValues = new[] { "1", "true" };
var falseValues = new[] { "0", "false" };
if(trueValues.Contains(str, StringComparer.OrdinalIgnoreCase))
return (T)(object)true;
if(falseValues.Contains(str, StringComparer.OrdinalIgnoreCase))
return (T)(object)false;
throw new FormatException();
}
else if(typeof(T) == typeof(Uri))
return (T)(object)new Uri(str, UriKind.Absolute);
else if(typeof(T) == typeof(string))
return (T)(object)str;
else if(typeof(T) == typeof(IPEndPoint))
{
var separator = str.LastIndexOf(":");
if(separator == -1)
throw new FormatException();
var ip = str.Substring(0, separator);
var port = str.Substring(separator + 1);
return (T)(object)new IPEndPoint(IPAddress.Parse(ip), int.Parse(port));
}
else if(typeof(T) == typeof(int))
{
return (T)(object)int.Parse(str, CultureInfo.InvariantCulture);
}
else
{
throw new NotSupportedException("Configuration value does not support time " + typeof(T).Name);
}
}
}
}

View file

@ -1,5 +1,8 @@
using BTCPayServer.Authentication;
using BTCPayServer.Configuration;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Text;
@ -8,6 +11,17 @@ namespace BTCPayServer
{
public static class Extensions
{
public static IServiceCollection ConfigureBTCPayServer(this IServiceCollection services, IConfiguration conf)
{
services.Configure<BTCPayServerOptions>(o =>
{
o.LoadArgs(conf);
});
return services;
}
public static BitIdentity GetBitIdentity(this Controller controller)
{
if(!(controller.User.Identity is BitIdentity))

View file

@ -22,70 +22,142 @@ using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Fees;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Rewrite;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Authorization;
using BTCPayServer.Controllers;
using BTCPayServer.Services.Mails;
using Microsoft.AspNetCore.Identity;
using BTCPayServer.Models;
using System.Threading.Tasks;
namespace BTCPayServer.Hosting
{
public static class BTCPayServerServices
{
public static IWebHostBuilder AddPayServer(this IWebHostBuilder builder, BTCPayServerOptions options)
public class OwnStoreAuthorizationRequirement : IAuthorizationRequirement
{
return
builder
.ConfigureServices(c =>
public OwnStoreAuthorizationRequirement()
{
}
public OwnStoreAuthorizationRequirement(string role)
{
Role = role;
}
public string Role
{
get; set;
}
}
public class OwnStoreHandler : AuthorizationHandler<OwnStoreAuthorizationRequirement>
{
StoreRepository _StoreRepository;
UserManager<ApplicationUser> _UserManager;
public OwnStoreHandler(StoreRepository storeRepository, UserManager<ApplicationUser> userManager)
{
_StoreRepository = storeRepository;
_UserManager = userManager;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OwnStoreAuthorizationRequirement requirement)
{
object storeId = null;
if(!((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).RouteData.Values.TryGetValue("storeId", out storeId))
context.Succeed(requirement);
else
{
c.AddDbContext<ApplicationDbContext>(o =>
{
var path = Path.Combine(options.DataDir, "sqllite.db");
o.UseSqlite("Data Source=" + path);
});
c.AddSingleton(options);
c.AddSingleton<BTCPayServerRuntime>(o =>
{
var runtime = new BTCPayServerRuntime();
runtime.Configure(options);
return runtime;
});
var store = await _StoreRepository.FindStore((string)storeId, _UserManager.GetUserId(((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).HttpContext.User));
if(store != null)
if(requirement.Role == null || requirement.Role == store.Role)
context.Succeed(requirement);
}
}
}
class BTCPayServerConfigureOptions : IConfigureOptions<MvcOptions>
{
BTCPayServerOptions _Options;
public BTCPayServerConfigureOptions(BTCPayServerOptions options)
{
_Options = options;
}
public void Configure(MvcOptions options)
{
if(_Options.RequireHttps)
options.Filters.Add(new RequireHttpsAttribute());
}
}
public static IServiceCollection AddBTCPayServer(this IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>((provider, o) =>
{
var path = Path.Combine(provider.GetRequiredService<BTCPayServerOptions>().DataDir, "sqllite.db");
o.UseSqlite("Data Source=" + path);
});
services.TryAddSingleton<BTCPayServerOptions>(o => o.GetRequiredService<IOptions<BTCPayServerOptions>>().Value);
services.TryAddSingleton<IConfigureOptions<MvcOptions>, BTCPayServerConfigureOptions>();
services.TryAddSingleton(o =>
{
var runtime = new BTCPayServerRuntime();
runtime.Configure(o.GetRequiredService<BTCPayServerOptions>());
return runtime;
});
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().TokenRepository);
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().InvoiceRepository);
services.TryAddSingleton<Network>(o => o.GetRequiredService<BTCPayServerOptions>().Network);
services.TryAddSingleton<ApplicationDbContextFactory>(o => o.GetRequiredService<BTCPayServerRuntime>().DBFactory);
services.TryAddSingleton<StoreRepository>();
services.TryAddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().Wallet);
services.TryAddSingleton<CurrencyNameTable>();
services.TryAddSingleton<IFeeProvider>(o => new NBXplorerFeeProvider()
{
Fallback = new FeeRate(100, 1),
BlockTarget = 20,
ExplorerClient = o.GetRequiredService<ExplorerClient>()
});
services.TryAddSingleton<ExplorerClient>(o =>
{
var runtime = o.GetRequiredService<BTCPayServerRuntime>();
return runtime.Explorer;
});
services.TryAddSingleton<Bitpay>(o =>
{
if(o.GetRequiredService<BTCPayServerOptions>().Network == Network.Main)
return new Bitpay(new Key(), new Uri("https://bitpay.com/"));
else
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));
});
services.TryAddSingleton<IRateProvider, BitpayRateProvider>();
services.TryAddSingleton<InvoiceWatcher>();
services.TryAddSingleton<IHostedService>(o => o.GetRequiredService<InvoiceWatcher>());
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
services.TryAddSingleton<IExternalUrlProvider>(o =>
{
var op = o.GetRequiredService<BTCPayServerOptions>();
if(op.ExternalUrl != null)
return new FixedExternalUrlProvider(op.ExternalUrl, o.GetRequiredService<IHttpContextAccessor>());
return new DefaultExternalUrlProvider(o.GetRequiredService<IHttpContextAccessor>());
});
services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>();
services.AddTransient<AccessTokenController>();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
if(options.RequireHttps)
{
c.Configure<MvcOptions>(o =>
{
o.Filters.Add(new RequireHttpsAttribute());
});
}
services.AddAuthorization(o =>
{
o.AddPolicy("CanAccessStore", builder =>
{
builder.AddRequirements(new OwnStoreAuthorizationRequirement());
});
c.AddSingleton<Network>(options.Network);
c.AddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().TokenRepository);
c.AddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().InvoiceRepository);
c.AddSingleton<ApplicationDbContextFactory>(o => o.GetRequiredService<BTCPayServerRuntime>().DBFactory);
c.AddSingleton<StoreRepository>();
c.AddSingleton(o => o.GetRequiredService<BTCPayServerRuntime>().Wallet);
c.AddSingleton<CurrencyNameTable>();
c.AddSingleton<IFeeProvider>(o => new NBXplorerFeeProvider()
{
Fallback = new FeeRate(100, 1),
BlockTarget = 20,
ExplorerClient = o.GetRequiredService<ExplorerClient>()
});
c.AddSingleton<ExplorerClient>(o =>
{
var runtime = o.GetRequiredService<BTCPayServerRuntime>();
return runtime.Explorer;
});
c.AddSingleton<Bitpay>(o =>
{
if(options.Network == Network.Main)
return new Bitpay(new Key(), new Uri("https://bitpay.com/"));
else
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));
});
c.TryAddSingleton<IRateProvider, BitpayRateProvider>();
c.AddSingleton<InvoiceWatcher>();
c.AddSingleton<IHostedService>(o => o.GetRequiredService<InvoiceWatcher>());
c.AddScoped<IHttpContextAccessor, HttpContextAccessor>();
c.AddSingleton<IExternalUrlProvider>(o => new FixedExternalUrlProvider(options.ExternalUrl, o.GetRequiredService<IHttpContextAccessor>()));
})
.UseUrls(options.GetUrls());
o.AddPolicy("OwnStore", builder =>
{
builder.AddRequirements(new OwnStoreAuthorizationRequirement("Owner"));
});
});
return services;
}
public static IApplicationBuilder UsePayServer(this IApplicationBuilder app)

View file

@ -23,45 +23,30 @@ using System.Threading.Tasks;
using BTCPayServer.Controllers;
using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Mails;
using Microsoft.Extensions.Configuration;
namespace BTCPayServer.Hosting
{
public class Startup
{
public Startup(IConfiguration conf)
{
Configuration = conf;
}
public IConfiguration Configuration
{
get; set;
}
public void ConfigureServices(IServiceCollection services)
{
services.ConfigureBTCPayServer(Configuration);
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthorization(o =>
{
o.AddPolicy("CanAccessStore", builder =>
{
builder.AddRequirements(new OwnStoreAuthorizationRequirement());
});
o.AddPolicy("OwnStore", builder =>
{
builder.AddRequirements(new OwnStoreAuthorizationRequirement("Owner"));
});
});
services.AddSingleton<IAuthorizationHandler, OwnStoreHandler>();
services.AddTransient<AccessTokenController>();
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
//services.AddSingleton<IObjectModelValidator, NoObjectModelValidator>();
services.AddMvcCore(o =>
{
//o.Filters.Add(new NBXplorerExceptionFilter());
o.OutputFormatters.Clear();
o.InputFormatters.Clear();
})
.AddJsonFormatters()
.AddFormatterMappings();
services.AddBTCPayServer();
services.AddMvc();
}
public void Configure(
@ -74,6 +59,8 @@ namespace BTCPayServer.Hosting
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
Logs.Configure(loggerFactory);
app.UsePayServer();
app.UseStaticFiles();
@ -87,45 +74,4 @@ namespace BTCPayServer.Hosting
});
}
}
public class OwnStoreAuthorizationRequirement : IAuthorizationRequirement
{
public OwnStoreAuthorizationRequirement()
{
}
public OwnStoreAuthorizationRequirement(string role)
{
Role = role;
}
public string Role
{
get; set;
}
}
public class OwnStoreHandler : AuthorizationHandler<OwnStoreAuthorizationRequirement>
{
StoreRepository _StoreRepository;
UserManager<ApplicationUser> _UserManager;
public OwnStoreHandler(StoreRepository storeRepository, UserManager<ApplicationUser> userManager)
{
_StoreRepository = storeRepository;
_UserManager = userManager;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OwnStoreAuthorizationRequirement requirement)
{
object storeId = null;
if(!((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).RouteData.Values.TryGetValue("storeId", out storeId))
context.Succeed(requirement);
else
{
var store = await _StoreRepository.FindStore((string)storeId, _UserManager.GetUserId(((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).HttpContext.User));
if(store != null)
if(requirement.Role == null || requirement.Role == store.Role)
context.Succeed(requirement);
}
}
}
}

View file

@ -13,6 +13,8 @@ using System.IO;
using System.Net;
using System.Collections.Generic;
using System.Collections;
using Microsoft.AspNetCore.Hosting.Server.Features;
using System.Threading;
namespace BTCPayServer
{
@ -22,18 +24,22 @@ namespace BTCPayServer
{
ServicePointManager.DefaultConnectionLimit = 100;
IWebHost host = null;
CustomConsoleLogProvider loggerProvider = new CustomConsoleLogProvider();
var loggerFactory = new LoggerFactory();
loggerFactory.AddProvider(loggerProvider);
var logger = loggerFactory.CreateLogger("Configuration");
try
{
var conf = new BTCPayServerOptions();
var arguments = new TextFileConfiguration(args);
arguments = LoadEnvironmentVariables(arguments);
conf.LoadArgs(arguments);
var conf = new DefaultConfiguration() { Logger = logger }.CreateConfiguration(args);
if(conf == null)
return;
host = new WebHostBuilder()
.AddPayServer(conf)
.UseKestrel()
.UseIISIntegration()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseConfiguration(conf)
.ConfigureServices(services =>
{
services.AddLogging(l =>
@ -44,11 +50,19 @@ namespace BTCPayServer
})
.UseStartup<Startup>()
.Build();
var running = host.RunAsync();
OpenBrowser(conf.GetUrls().Select(url => url.Replace("0.0.0.0", "127.0.0.1")).First());
running.GetAwaiter().GetResult();
}
catch(ConfigurationException ex)
host.StartAsync().GetAwaiter().GetResult();
var urls = host.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
if(urls.Count != 0)
{
OpenBrowser(urls.Select(url => url.Replace("0.0.0.0", "127.0.0.1")).First());
}
foreach(var url in urls)
{
logger.LogInformation("Listening on " + url);
}
host.WaitForShutdown();
}
catch(ConfigException ex)
{
if(!string.IsNullOrEmpty(ex.Message))
Logs.Configuration.LogError(ex.Message);
@ -62,30 +76,10 @@ namespace BTCPayServer
{
if(host != null)
host.Dispose();
loggerProvider.Dispose();
}
}
private static TextFileConfiguration LoadEnvironmentVariables(TextFileConfiguration args)
{
var variables = Environment.GetEnvironmentVariables();
List<string> values = new List<string>();
foreach(DictionaryEntry variable in variables)
{
var key = (string)variable.Key;
var value = (string)variable.Value;
if(key.StartsWith("APPSETTING_", StringComparison.Ordinal))
{
key = key.Substring("APPSETTING_".Length);
values.Add("-" + key);
values.Add(value);
}
}
TextFileConfiguration envConfig = new TextFileConfiguration(values.ToArray());
args.MergeInto(envConfig, true);
return envConfig;
}
public static void OpenBrowser(string url)
{
try

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB