mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-23 22:46:49 +01:00
Merge remote-tracking branch 'source/master' into dev-lndrpc
This commit is contained in:
commit
07e13747cf
16 changed files with 402 additions and 205 deletions
|
@ -343,6 +343,7 @@ namespace BTCPayServer.Tests
|
||||||
var user = tester.NewAccount();
|
var user = tester.NewAccount();
|
||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
user.RegisterDerivationScheme("BTC");
|
user.RegisterDerivationScheme("BTC");
|
||||||
|
Assert.True(user.BitPay.TestAccess(Facade.Merchant));
|
||||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||||
{
|
{
|
||||||
Buyer = new Buyer() { email = "test@fwf.com" },
|
Buyer = new Buyer() { email = "test@fwf.com" },
|
||||||
|
@ -755,8 +756,30 @@ namespace BTCPayServer.Tests
|
||||||
Assert.False(user.BitPay.TestAccess(Facade.Merchant));
|
Assert.False(user.BitPay.TestAccess(Facade.Merchant));
|
||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
user.RegisterDerivationScheme("BTC");
|
user.RegisterDerivationScheme("BTC");
|
||||||
|
|
||||||
Assert.True(user.BitPay.TestAccess(Facade.Merchant));
|
Assert.True(user.BitPay.TestAccess(Facade.Merchant));
|
||||||
|
|
||||||
|
// Test request pairing code client side
|
||||||
|
var storeController = user.GetController<StoresController>();
|
||||||
|
storeController.CreateToken(new CreateTokenViewModel()
|
||||||
|
{
|
||||||
|
Facade = Facade.Merchant.ToString(),
|
||||||
|
Label = "test2",
|
||||||
|
StoreId = user.StoreId
|
||||||
|
}).GetAwaiter().GetResult();
|
||||||
|
Assert.NotNull(storeController.GeneratedPairingCode);
|
||||||
|
|
||||||
|
|
||||||
|
var k = new Key();
|
||||||
|
var bitpay = new Bitpay(k, tester.PayTester.ServerUri);
|
||||||
|
bitpay.AuthorizeClient(new PairingCode(storeController.GeneratedPairingCode)).Wait();
|
||||||
|
Assert.True(bitpay.TestAccess(Facade.Merchant));
|
||||||
|
Assert.True(bitpay.TestAccess(Facade.PointOfSale));
|
||||||
|
// Same with new instance
|
||||||
|
bitpay = new Bitpay(k, tester.PayTester.ServerUri);
|
||||||
|
Assert.True(bitpay.TestAccess(Facade.Merchant));
|
||||||
|
Assert.True(bitpay.TestAccess(Facade.PointOfSale));
|
||||||
|
|
||||||
// Can generate API Key
|
// Can generate API Key
|
||||||
var repo = tester.PayTester.GetService<TokenRepository>();
|
var repo = tester.PayTester.GetService<TokenRepository>();
|
||||||
Assert.Empty(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult());
|
Assert.Empty(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult());
|
||||||
|
|
34
BTCPayServer/BTCPayNetworkProvider.Feathercoin.cs
Normal file
34
BTCPayServer/BTCPayNetworkProvider.Feathercoin.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Services.Rates;
|
||||||
|
using NBitcoin;
|
||||||
|
using NBXplorer;
|
||||||
|
|
||||||
|
namespace BTCPayServer
|
||||||
|
{
|
||||||
|
public partial class BTCPayNetworkProvider
|
||||||
|
{
|
||||||
|
public void InitFeathercoin()
|
||||||
|
{
|
||||||
|
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("FTC");
|
||||||
|
Add(new BTCPayNetwork()
|
||||||
|
{
|
||||||
|
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||||
|
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.feathercoin.com/tx/{0}" : "https://explorer.feathercoin.com/tx/{0}",
|
||||||
|
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||||
|
NBXplorerNetwork = nbxplorerNetwork,
|
||||||
|
UriScheme = "feathercoin",
|
||||||
|
DefaultRateRules = new[]
|
||||||
|
{
|
||||||
|
"FTC_X = FTC_BTC * BTC_X",
|
||||||
|
"FTC_BTC = bittrex(FTC_BTC)"
|
||||||
|
},
|
||||||
|
CryptoImagePath = "imlegacy/feathercoin.png",
|
||||||
|
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||||
|
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("8'") : new KeyPath("1'")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
BTCPayServer/BTCPayNetworkProvider.Ufo.cs
Normal file
34
BTCPayServer/BTCPayNetworkProvider.Ufo.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Services.Rates;
|
||||||
|
using NBitcoin;
|
||||||
|
using NBXplorer;
|
||||||
|
|
||||||
|
namespace BTCPayServer
|
||||||
|
{
|
||||||
|
public partial class BTCPayNetworkProvider
|
||||||
|
{
|
||||||
|
public void InitUfo()
|
||||||
|
{
|
||||||
|
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("UFO");
|
||||||
|
Add(new BTCPayNetwork()
|
||||||
|
{
|
||||||
|
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||||
|
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://chainz.cryptoid.info/ufo/tx.dws?{0}" : "https://chainz.cryptoid.info/ufo/tx.dws?{0}",
|
||||||
|
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||||
|
NBXplorerNetwork = nbxplorerNetwork,
|
||||||
|
UriScheme = "ufo",
|
||||||
|
DefaultRateRules = new[]
|
||||||
|
{
|
||||||
|
"UFO_X = UFO_BTC * BTC_X",
|
||||||
|
"UFO_BTC = coinexchange(UFO_BTC)"
|
||||||
|
},
|
||||||
|
CryptoImagePath = "imlegacy/ufo.png",
|
||||||
|
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||||
|
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("202'") : new KeyPath("1'")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,6 +51,8 @@ namespace BTCPayServer
|
||||||
InitBitcoinGold();
|
InitBitcoinGold();
|
||||||
InitMonacoin();
|
InitMonacoin();
|
||||||
InitPolis();
|
InitPolis();
|
||||||
|
InitFeathercoin();
|
||||||
|
InitUfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||||
<Version>1.0.2.31</Version>
|
<Version>1.0.2.34</Version>
|
||||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.6.0" />
|
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.6.0" />
|
||||||
<PackageReference Include="NBitcoin" Version="4.1.1.10" />
|
<PackageReference Include="NBitcoin" Version="4.1.1.10" />
|
||||||
<PackageReference Include="NBitpayClient" Version="1.0.0.26" />
|
<PackageReference Include="NBitpayClient" Version="1.0.0.28" />
|
||||||
<PackageReference Include="DBreeze" Version="1.87.0" />
|
<PackageReference Include="DBreeze" Version="1.87.0" />
|
||||||
<PackageReference Include="NBXplorer.Client" Version="1.0.2.10" />
|
<PackageReference Include="NBXplorer.Client" Version="1.0.2.10" />
|
||||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2" />
|
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2" />
|
||||||
|
|
|
@ -12,7 +12,8 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
[BitpayAPIConstraint]
|
[Authorize(AuthenticationSchemes = Security.Policies.BitpayAuthentication)]
|
||||||
|
[BitpayAPIConstraint(true)]
|
||||||
public class AccessTokenController : Controller
|
public class AccessTokenController : Controller
|
||||||
{
|
{
|
||||||
TokenRepository _TokenRepository;
|
TokenRepository _TokenRepository;
|
||||||
|
@ -30,6 +31,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("tokens")]
|
[Route("tokens")]
|
||||||
|
[AllowAnonymous]
|
||||||
public async Task<DataWrapper<List<PairingCodeResponse>>> Tokens([FromBody] TokenRequest request)
|
public async Task<DataWrapper<List<PairingCodeResponse>>> Tokens([FromBody] TokenRequest request)
|
||||||
{
|
{
|
||||||
PairingCodeEntity pairingEntity = null;
|
PairingCodeEntity pairingEntity = null;
|
||||||
|
@ -53,7 +55,7 @@ namespace BTCPayServer.Controllers
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var sin = this.User.GetSIN() ?? request.Id;
|
var sin = this.User.GetSIN() ?? request.Id;
|
||||||
if (string.IsNullOrEmpty(request.Id) || !NBitpayClient.Extensions.BitIdExtensions.ValidateSIN(request.Id))
|
if (string.IsNullOrEmpty(sin) || !NBitpayClient.Extensions.BitIdExtensions.ValidateSIN(sin))
|
||||||
throw new BitpayHttpException(400, "'id' property is required, alternatively, use BitId");
|
throw new BitpayHttpException(400, "'id' property is required, alternatively, use BitId");
|
||||||
|
|
||||||
pairingEntity = await _TokenRepository.GetPairingAsync(request.PairingCode);
|
pairingEntity = await _TokenRepository.GetPairingAsync(request.PairingCode);
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
[EnableCors("BitpayAPI")]
|
[EnableCors("BitpayAPI")]
|
||||||
[BitpayAPIConstraint]
|
[BitpayAPIConstraint]
|
||||||
[Authorize(Policies.CanUseStore.Key)]
|
[Authorize(Policies.CanUseStore.Key, AuthenticationSchemes = Policies.BitpayAuthentication)]
|
||||||
public class InvoiceControllerAPI : Controller
|
public class InvoiceControllerAPI : Controller
|
||||||
{
|
{
|
||||||
private InvoiceController _InvoiceController;
|
private InvoiceController _InvoiceController;
|
||||||
|
|
|
@ -6,6 +6,11 @@ using System.Threading.Tasks;
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
using Hangfire.MemoryStorage;
|
using Hangfire.MemoryStorage;
|
||||||
using Hangfire.PostgreSql;
|
using Hangfire.PostgreSql;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Operations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
|
||||||
namespace BTCPayServer.Data
|
namespace BTCPayServer.Data
|
||||||
{
|
{
|
||||||
|
@ -31,12 +36,56 @@ namespace BTCPayServer.Data
|
||||||
return new ApplicationDbContext(builder.Options);
|
return new ApplicationDbContext(builder.Options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CustomNpgsqlMigrationsSqlGenerator : NpgsqlMigrationsSqlGenerator
|
||||||
|
{
|
||||||
|
public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies) : base(dependencies)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Generate(NpgsqlCreateDatabaseOperation operation, IModel model, MigrationCommandListBuilder builder)
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.Append("CREATE DATABASE ")
|
||||||
|
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name));
|
||||||
|
|
||||||
|
// POSTGRES gotcha: Indexed Text column (even if PK) are not used if we are not using C locale
|
||||||
|
builder
|
||||||
|
.Append(" TEMPLATE ")
|
||||||
|
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("template0"));
|
||||||
|
|
||||||
|
builder
|
||||||
|
.Append(" LC_CTYPE ")
|
||||||
|
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("C"));
|
||||||
|
|
||||||
|
builder
|
||||||
|
.Append(" LC_COLLATE ")
|
||||||
|
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("C"));
|
||||||
|
|
||||||
|
builder
|
||||||
|
.Append(" ENCODING ")
|
||||||
|
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("UTF8"));
|
||||||
|
|
||||||
|
if (operation.Tablespace != null)
|
||||||
|
{
|
||||||
|
builder
|
||||||
|
.Append(" TABLESPACE ")
|
||||||
|
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Tablespace));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
|
||||||
|
|
||||||
|
EndStatement(builder, suppressTransaction: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void ConfigureBuilder(DbContextOptionsBuilder builder)
|
public void ConfigureBuilder(DbContextOptionsBuilder builder)
|
||||||
{
|
{
|
||||||
if (_Type == DatabaseType.Sqlite)
|
if (_Type == DatabaseType.Sqlite)
|
||||||
builder.UseSqlite(_ConnectionString);
|
builder.UseSqlite(_ConnectionString);
|
||||||
else if (_Type == DatabaseType.Postgres)
|
else if (_Type == DatabaseType.Postgres)
|
||||||
builder.UseNpgsql(_ConnectionString);
|
builder
|
||||||
|
.UseNpgsql(_ConnectionString)
|
||||||
|
.ReplaceService<IMigrationsSqlGenerator, CustomNpgsqlMigrationsSqlGenerator>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConfigureHangfireBuilder(IGlobalConfiguration builder)
|
public void ConfigureHangfireBuilder(IGlobalConfiguration builder)
|
||||||
|
|
|
@ -117,7 +117,6 @@ namespace BTCPayServer.Hosting
|
||||||
services.AddSingleton<IHostedService, InvoiceWatcher>();
|
services.AddSingleton<IHostedService, InvoiceWatcher>();
|
||||||
services.AddSingleton<IHostedService, RatesHostedService>();
|
services.AddSingleton<IHostedService, RatesHostedService>();
|
||||||
services.AddTransient<IConfigureOptions<MvcOptions>, BTCPayClaimsFilter>();
|
services.AddTransient<IConfigureOptions<MvcOptions>, BTCPayClaimsFilter>();
|
||||||
services.AddTransient<IConfigureOptions<MvcOptions>, BitpayClaimsFilter>();
|
|
||||||
|
|
||||||
services.TryAddSingleton<ExplorerClientProvider>();
|
services.TryAddSingleton<ExplorerClientProvider>();
|
||||||
services.TryAddSingleton<Bitpay>(o =>
|
services.TryAddSingleton<Bitpay>(o =>
|
||||||
|
@ -137,6 +136,7 @@ namespace BTCPayServer.Hosting
|
||||||
// bundling
|
// bundling
|
||||||
|
|
||||||
services.AddAuthorization(o => Policies.AddBTCPayPolicies(o));
|
services.AddAuthorization(o => Policies.AddBTCPayPolicies(o));
|
||||||
|
BitpayAuthentication.AddAuthentication(services);
|
||||||
|
|
||||||
services.AddBundles();
|
services.AddBundles();
|
||||||
services.AddTransient<BundleOptions>(provider =>
|
services.AddTransient<BundleOptions>(provider =>
|
||||||
|
|
247
BTCPayServer/Security/BitpayAuthentication.cs
Normal file
247
BTCPayServer/Security/BitpayAuthentication.cs
Normal file
|
@ -0,0 +1,247 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
|
using Microsoft.AspNetCore.Http.Extensions;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Authentication;
|
||||||
|
using BTCPayServer.Models;
|
||||||
|
using BTCPayServer.Services;
|
||||||
|
using BTCPayServer.Services.Stores;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using NBitcoin;
|
||||||
|
using NBitcoin.DataEncoders;
|
||||||
|
using NBitpayClient;
|
||||||
|
using NBitpayClient.Extensions;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using BTCPayServer.Logging;
|
||||||
|
using Microsoft.AspNetCore.Http.Internal;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Security
|
||||||
|
{
|
||||||
|
public class BitpayAuthentication
|
||||||
|
{
|
||||||
|
public class BitpayAuthOptions : AuthenticationSchemeOptions
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
class BitpayAuthHandler : AuthenticationHandler<BitpayAuthOptions>
|
||||||
|
{
|
||||||
|
StoreRepository _StoreRepository;
|
||||||
|
TokenRepository _TokenRepository;
|
||||||
|
public BitpayAuthHandler(
|
||||||
|
TokenRepository tokenRepository,
|
||||||
|
StoreRepository storeRepository,
|
||||||
|
IOptionsMonitor<BitpayAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
|
||||||
|
{
|
||||||
|
_TokenRepository = tokenRepository;
|
||||||
|
_StoreRepository = storeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||||
|
{
|
||||||
|
if (Context.Request.HttpContext.GetIsBitpayAPI())
|
||||||
|
{
|
||||||
|
List<Claim> claims = new List<Claim>();
|
||||||
|
var bitpayAuth = Context.Request.HttpContext.GetBitpayAuth();
|
||||||
|
string storeId = null;
|
||||||
|
// Careful, those are not the opposite. failedAuth says if a the tentative failed.
|
||||||
|
// successAuth, ensure that at least one succeed.
|
||||||
|
var failedAuth = false;
|
||||||
|
var successAuth = false;
|
||||||
|
if (!string.IsNullOrEmpty(bitpayAuth.Signature) && !string.IsNullOrEmpty(bitpayAuth.Id))
|
||||||
|
{
|
||||||
|
var result = await CheckBitId(Context.Request.HttpContext, bitpayAuth.Signature, bitpayAuth.Id, claims);
|
||||||
|
storeId = result.StoreId;
|
||||||
|
failedAuth = !result.SuccessAuth;
|
||||||
|
successAuth = result.SuccessAuth;
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(bitpayAuth.Authorization))
|
||||||
|
{
|
||||||
|
storeId = await CheckLegacyAPIKey(Context.Request.HttpContext, bitpayAuth.Authorization);
|
||||||
|
if (storeId == null)
|
||||||
|
{
|
||||||
|
Logs.PayServer.LogDebug("API key check failed");
|
||||||
|
failedAuth = true;
|
||||||
|
}
|
||||||
|
successAuth = storeId != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failedAuth)
|
||||||
|
{
|
||||||
|
return AuthenticateResult.Fail("Invalid credentials");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (successAuth)
|
||||||
|
{
|
||||||
|
if (storeId != null)
|
||||||
|
{
|
||||||
|
claims.Add(new Claim(Policies.CanUseStore.Key, storeId));
|
||||||
|
var store = await _StoreRepository.FindStore(storeId);
|
||||||
|
Context.Request.HttpContext.SetStoreData(store);
|
||||||
|
}
|
||||||
|
return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, Policies.BitpayAuthentication)), Policies.BitpayAuthentication));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AuthenticateResult.NoResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<(string StoreId, bool SuccessAuth)> CheckBitId(HttpContext httpContext, string sig, string id, List<Claim> claims)
|
||||||
|
{
|
||||||
|
httpContext.Request.EnableRewind();
|
||||||
|
|
||||||
|
string storeId = null;
|
||||||
|
string body = string.Empty;
|
||||||
|
if (httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null)
|
||||||
|
{
|
||||||
|
using (StreamReader reader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, true, 1024, true))
|
||||||
|
{
|
||||||
|
body = reader.ReadToEnd();
|
||||||
|
}
|
||||||
|
httpContext.Request.Body.Position = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var url = httpContext.Request.GetEncodedUrl();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var key = new PubKey(id);
|
||||||
|
if (BitIdExtensions.CheckBitIDSignature(key, sig, url, body))
|
||||||
|
{
|
||||||
|
var sin = key.GetBitIDSIN();
|
||||||
|
claims.Add(new Claim(Claims.SIN, sin));
|
||||||
|
|
||||||
|
string token = null;
|
||||||
|
if (httpContext.Request.Query.TryGetValue("token", out var tokenValues))
|
||||||
|
{
|
||||||
|
token = tokenValues[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token == null && !String.IsNullOrEmpty(body) && httpContext.Request.Method == "POST")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
token = JObject.Parse(body)?.Property("token")?.Value?.Value<string>();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token != null)
|
||||||
|
{
|
||||||
|
var bitToken = await GetTokenPermissionAsync(sin, token);
|
||||||
|
if (bitToken == null)
|
||||||
|
{
|
||||||
|
return (null, false);
|
||||||
|
}
|
||||||
|
storeId = bitToken.StoreId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (FormatException) { }
|
||||||
|
return (storeId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> CheckLegacyAPIKey(HttpContext httpContext, string auth)
|
||||||
|
{
|
||||||
|
var splitted = auth.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
if (splitted.Length != 2 || !splitted[0].Equals("Basic", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string apiKey = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
apiKey = Encoders.ASCII.EncodeData(Encoders.Base64.DecodeData(splitted[1]));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await _TokenRepository.GetStoreIdFromAPIKey(apiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<BitTokenEntity> GetTokenPermissionAsync(string sin, string expectedToken)
|
||||||
|
{
|
||||||
|
var actualTokens = (await _TokenRepository.GetTokens(sin)).ToArray();
|
||||||
|
actualTokens = actualTokens.SelectMany(t => GetCompatibleTokens(t)).ToArray();
|
||||||
|
|
||||||
|
var actualToken = actualTokens.FirstOrDefault(a => a.Value.Equals(expectedToken, StringComparison.Ordinal));
|
||||||
|
if (expectedToken == null || actualToken == null)
|
||||||
|
{
|
||||||
|
Logs.PayServer.LogDebug($"No token found for facade {Facade.Merchant} for SIN {sin}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return actualToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<BitTokenEntity> GetCompatibleTokens(BitTokenEntity token)
|
||||||
|
{
|
||||||
|
if (token.Facade == Facade.Merchant.ToString())
|
||||||
|
{
|
||||||
|
yield return token.Clone(Facade.User);
|
||||||
|
yield return token.Clone(Facade.PointOfSale);
|
||||||
|
}
|
||||||
|
if (token.Facade == Facade.PointOfSale.ToString())
|
||||||
|
{
|
||||||
|
yield return token.Clone(Facade.User);
|
||||||
|
}
|
||||||
|
yield return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsBitpayAPI(HttpContext httpContext, bool bitpayAuth)
|
||||||
|
{
|
||||||
|
if (!httpContext.Request.Path.HasValue)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var isJson = (httpContext.Request.ContentType ?? string.Empty).StartsWith("application/json", StringComparison.OrdinalIgnoreCase);
|
||||||
|
var path = httpContext.Request.Path.Value;
|
||||||
|
if (
|
||||||
|
bitpayAuth &&
|
||||||
|
path == "/invoices" &&
|
||||||
|
httpContext.Request.Method == "POST" &&
|
||||||
|
isJson)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (
|
||||||
|
bitpayAuth &&
|
||||||
|
path == "/invoices" &&
|
||||||
|
httpContext.Request.Method == "GET")
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (
|
||||||
|
path.StartsWith("/invoices/", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
httpContext.Request.Method == "GET" &&
|
||||||
|
(isJson || httpContext.Request.Query.ContainsKey("token")))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (path.Equals("/rates", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
httpContext.Request.Method == "GET")
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (
|
||||||
|
path.Equals("/tokens", StringComparison.Ordinal) &&
|
||||||
|
(httpContext.Request.Method == "GET" || httpContext.Request.Method == "POST"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internal static void AddAuthentication(IServiceCollection services, Action<BitpayAuthOptions> bitpayAuth = null)
|
||||||
|
{
|
||||||
|
bitpayAuth = bitpayAuth ?? new Action<BitpayAuthOptions>((o) => { });
|
||||||
|
services.AddAuthentication().AddScheme<BitpayAuthOptions, BitpayAuthHandler>(Policies.BitpayAuthentication, bitpayAuth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,196 +0,0 @@
|
||||||
using System;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
|
||||||
using Microsoft.AspNetCore.Http.Extensions;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using BTCPayServer.Authentication;
|
|
||||||
using BTCPayServer.Models;
|
|
||||||
using BTCPayServer.Services;
|
|
||||||
using BTCPayServer.Services.Stores;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Filters;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using NBitcoin;
|
|
||||||
using NBitcoin.DataEncoders;
|
|
||||||
using NBitpayClient;
|
|
||||||
using NBitpayClient.Extensions;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using BTCPayServer.Logging;
|
|
||||||
using Microsoft.AspNetCore.Http.Internal;
|
|
||||||
|
|
||||||
namespace BTCPayServer.Security
|
|
||||||
{
|
|
||||||
public class BitpayClaimsFilter : IAsyncAuthorizationFilter, IConfigureOptions<MvcOptions>
|
|
||||||
{
|
|
||||||
UserManager<ApplicationUser> _UserManager;
|
|
||||||
StoreRepository _StoreRepository;
|
|
||||||
TokenRepository _TokenRepository;
|
|
||||||
|
|
||||||
public BitpayClaimsFilter(
|
|
||||||
UserManager<ApplicationUser> userManager,
|
|
||||||
TokenRepository tokenRepository,
|
|
||||||
StoreRepository storeRepository)
|
|
||||||
{
|
|
||||||
_UserManager = userManager;
|
|
||||||
_StoreRepository = storeRepository;
|
|
||||||
_TokenRepository = tokenRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
void IConfigureOptions<MvcOptions>.Configure(MvcOptions options)
|
|
||||||
{
|
|
||||||
options.Filters.Add(typeof(BitpayClaimsFilter));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
|
||||||
{
|
|
||||||
var principal = context.HttpContext.User;
|
|
||||||
if (context.HttpContext.GetIsBitpayAPI())
|
|
||||||
{
|
|
||||||
var bitpayAuth = context.HttpContext.GetBitpayAuth();
|
|
||||||
string storeId = null;
|
|
||||||
var failedAuth = false;
|
|
||||||
if (!string.IsNullOrEmpty(bitpayAuth.Signature) && !string.IsNullOrEmpty(bitpayAuth.Id))
|
|
||||||
{
|
|
||||||
storeId = await CheckBitId(context.HttpContext, bitpayAuth.Signature, bitpayAuth.Id);
|
|
||||||
if (!context.HttpContext.User.Claims.Any(c => c.Type == Claims.SIN))
|
|
||||||
{
|
|
||||||
Logs.PayServer.LogDebug("BitId signature check failed");
|
|
||||||
failedAuth = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!string.IsNullOrEmpty(bitpayAuth.Authorization))
|
|
||||||
{
|
|
||||||
storeId = await CheckLegacyAPIKey(context.HttpContext, bitpayAuth.Authorization);
|
|
||||||
if (storeId == null)
|
|
||||||
{
|
|
||||||
Logs.PayServer.LogDebug("API key check failed");
|
|
||||||
failedAuth = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (storeId != null)
|
|
||||||
{
|
|
||||||
var identity = ((ClaimsIdentity)context.HttpContext.User.Identity);
|
|
||||||
identity.AddClaim(new Claim(Policies.CanUseStore.Key, storeId));
|
|
||||||
var store = await _StoreRepository.FindStore(storeId);
|
|
||||||
context.HttpContext.SetStoreData(store);
|
|
||||||
}
|
|
||||||
else if (failedAuth)
|
|
||||||
{
|
|
||||||
throw new BitpayHttpException(401, "Invalid credentials");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> CheckBitId(HttpContext httpContext, string sig, string id)
|
|
||||||
{
|
|
||||||
httpContext.Request.EnableRewind();
|
|
||||||
|
|
||||||
string storeId = null;
|
|
||||||
string body = string.Empty;
|
|
||||||
if (httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null)
|
|
||||||
{
|
|
||||||
using (StreamReader reader = new StreamReader(httpContext.Request.Body, Encoding.UTF8, true, 1024, true))
|
|
||||||
{
|
|
||||||
body = reader.ReadToEnd();
|
|
||||||
}
|
|
||||||
httpContext.Request.Body.Position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = httpContext.Request.GetEncodedUrl();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var key = new PubKey(id);
|
|
||||||
if (BitIdExtensions.CheckBitIDSignature(key, sig, url, body))
|
|
||||||
{
|
|
||||||
var sin = key.GetBitIDSIN();
|
|
||||||
var identity = ((ClaimsIdentity)httpContext.User.Identity);
|
|
||||||
identity.AddClaim(new Claim(Claims.SIN, sin));
|
|
||||||
|
|
||||||
string token = null;
|
|
||||||
if (httpContext.Request.Query.TryGetValue("token", out var tokenValues))
|
|
||||||
{
|
|
||||||
token = tokenValues[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token == null && !String.IsNullOrEmpty(body) && httpContext.Request.Method == "POST")
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
token = JObject.Parse(body)?.Property("token")?.Value?.Value<string>();
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token != null)
|
|
||||||
{
|
|
||||||
var bitToken = await GetTokenPermissionAsync(sin, token);
|
|
||||||
if (bitToken == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
storeId = bitToken.StoreId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (FormatException) { }
|
|
||||||
return storeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<string> CheckLegacyAPIKey(HttpContext httpContext, string auth)
|
|
||||||
{
|
|
||||||
var splitted = auth.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
if (splitted.Length != 2 || !splitted[0].Equals("Basic", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
string apiKey = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
apiKey = Encoders.ASCII.EncodeData(Encoders.Base64.DecodeData(splitted[1]));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return await _TokenRepository.GetStoreIdFromAPIKey(apiKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<BitTokenEntity> GetTokenPermissionAsync(string sin, string expectedToken)
|
|
||||||
{
|
|
||||||
var actualTokens = (await _TokenRepository.GetTokens(sin)).ToArray();
|
|
||||||
actualTokens = actualTokens.SelectMany(t => GetCompatibleTokens(t)).ToArray();
|
|
||||||
|
|
||||||
var actualToken = actualTokens.FirstOrDefault(a => a.Value.Equals(expectedToken, StringComparison.Ordinal));
|
|
||||||
if (expectedToken == null || actualToken == null)
|
|
||||||
{
|
|
||||||
Logs.PayServer.LogDebug($"No token found for facade {Facade.Merchant} for SIN {sin}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return actualToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<BitTokenEntity> GetCompatibleTokens(BitTokenEntity token)
|
|
||||||
{
|
|
||||||
if (token.Facade == Facade.Merchant.ToString())
|
|
||||||
{
|
|
||||||
yield return token.Clone(Facade.User);
|
|
||||||
yield return token.Clone(Facade.PointOfSale);
|
|
||||||
}
|
|
||||||
if (token.Facade == Facade.PointOfSale.ToString())
|
|
||||||
{
|
|
||||||
yield return token.Clone(Facade.User);
|
|
||||||
}
|
|
||||||
yield return token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,6 +8,7 @@ namespace BTCPayServer.Security
|
||||||
{
|
{
|
||||||
public static class Policies
|
public static class Policies
|
||||||
{
|
{
|
||||||
|
public const string BitpayAuthentication = "Bitpay.Auth";
|
||||||
public const string CookieAuthentication = "Identity.Application";
|
public const string CookieAuthentication = "Identity.Application";
|
||||||
public static AuthorizationOptions AddBTCPayPolicies(this AuthorizationOptions options)
|
public static AuthorizationOptions AddBTCPayPolicies(this AuthorizationOptions options)
|
||||||
{
|
{
|
||||||
|
|
|
@ -85,7 +85,8 @@ namespace BTCPayServer.Services.Rates
|
||||||
{
|
{
|
||||||
JToken bid = p.Value["bid"];
|
JToken bid = p.Value["bid"];
|
||||||
JToken ask = p.Value["ask"];
|
JToken ask = p.Value["ask"];
|
||||||
if (!decimal.TryParse(bid.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v1) ||
|
if (bid == null || ask == null ||
|
||||||
|
!decimal.TryParse(bid.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v1) ||
|
||||||
!decimal.TryParse(ask.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v2) ||
|
!decimal.TryParse(ask.Value<string>(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v2) ||
|
||||||
v1 > v2 ||
|
v1 > v2 ||
|
||||||
v1 <= 0 || v2 <= 0)
|
v1 <= 0 || v2 <= 0)
|
||||||
|
|
BIN
BTCPayServer/wwwroot/imlegacy/feathercoin.png
Normal file
BIN
BTCPayServer/wwwroot/imlegacy/feathercoin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 147 KiB |
BIN
BTCPayServer/wwwroot/imlegacy/ufo.png
Normal file
BIN
BTCPayServer/wwwroot/imlegacy/ufo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -30,7 +30,7 @@ You can also checkout [The Merchants Guide to accepting Bitcoin directly with no
|
||||||
|
|
||||||
While the documentation advise using docker-compose, you may want to build yourself outside of development purpose.
|
While the documentation advise using docker-compose, you may want to build yourself outside of development purpose.
|
||||||
|
|
||||||
First install .NET Core SDK 2.1 as specified by [Microsoft website](https://www.microsoft.com/net/download/dotnet-core/sdk-2.1.300-rc1).
|
First install .NET Core SDK 2.1 as specified by [Microsoft website](https://www.microsoft.com/net/download/dotnet-core).
|
||||||
|
|
||||||
On Powershell:
|
On Powershell:
|
||||||
```
|
```
|
||||||
|
|
Loading…
Add table
Reference in a new issue