diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index a25385eb7..058edc9d5 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -343,6 +343,7 @@ namespace BTCPayServer.Tests var user = tester.NewAccount(); user.GrantAccess(); user.RegisterDerivationScheme("BTC"); + Assert.True(user.BitPay.TestAccess(Facade.Merchant)); var invoice = user.BitPay.CreateInvoice(new Invoice() { Buyer = new Buyer() { email = "test@fwf.com" }, @@ -755,8 +756,30 @@ namespace BTCPayServer.Tests Assert.False(user.BitPay.TestAccess(Facade.Merchant)); user.GrantAccess(); user.RegisterDerivationScheme("BTC"); + Assert.True(user.BitPay.TestAccess(Facade.Merchant)); + // Test request pairing code client side + var storeController = user.GetController(); + 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 var repo = tester.PayTester.GetService(); Assert.Empty(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult()); diff --git a/BTCPayServer/BTCPayNetworkProvider.Feathercoin.cs b/BTCPayServer/BTCPayNetworkProvider.Feathercoin.cs new file mode 100644 index 000000000..2f48274b1 --- /dev/null +++ b/BTCPayServer/BTCPayNetworkProvider.Feathercoin.cs @@ -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'") + }); + } + } +} diff --git a/BTCPayServer/BTCPayNetworkProvider.Ufo.cs b/BTCPayServer/BTCPayNetworkProvider.Ufo.cs new file mode 100644 index 000000000..45a3e6d6f --- /dev/null +++ b/BTCPayServer/BTCPayNetworkProvider.Ufo.cs @@ -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'") + }); + } + } +} diff --git a/BTCPayServer/BTCPayNetworkProvider.cs b/BTCPayServer/BTCPayNetworkProvider.cs index aec5a9d18..5a1c997ce 100644 --- a/BTCPayServer/BTCPayNetworkProvider.cs +++ b/BTCPayServer/BTCPayNetworkProvider.cs @@ -51,6 +51,8 @@ namespace BTCPayServer InitBitcoinGold(); InitMonacoin(); InitPolis(); + InitFeathercoin(); + InitUfo(); } /// diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 3a9fb0a14..def5d1e8f 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.1 - 1.0.2.31 + 1.0.2.34 NU1701,CA1816,CA1308,CA1810,CA2208 @@ -41,7 +41,7 @@ - + diff --git a/BTCPayServer/Controllers/AccessTokenController.cs b/BTCPayServer/Controllers/AccessTokenController.cs index 3ae9494c8..a54637ea0 100644 --- a/BTCPayServer/Controllers/AccessTokenController.cs +++ b/BTCPayServer/Controllers/AccessTokenController.cs @@ -12,7 +12,8 @@ using System.Threading.Tasks; namespace BTCPayServer.Controllers { - [BitpayAPIConstraint] + [Authorize(AuthenticationSchemes = Security.Policies.BitpayAuthentication)] + [BitpayAPIConstraint(true)] public class AccessTokenController : Controller { TokenRepository _TokenRepository; @@ -30,6 +31,7 @@ namespace BTCPayServer.Controllers [HttpPost] [Route("tokens")] + [AllowAnonymous] public async Task>> Tokens([FromBody] TokenRequest request) { PairingCodeEntity pairingEntity = null; @@ -53,7 +55,7 @@ namespace BTCPayServer.Controllers else { 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"); pairingEntity = await _TokenRepository.GetPairingAsync(request.PairingCode); diff --git a/BTCPayServer/Controllers/InvoiceController.API.cs b/BTCPayServer/Controllers/InvoiceController.API.cs index aac4ee764..20c7e797a 100644 --- a/BTCPayServer/Controllers/InvoiceController.API.cs +++ b/BTCPayServer/Controllers/InvoiceController.API.cs @@ -20,7 +20,7 @@ namespace BTCPayServer.Controllers { [EnableCors("BitpayAPI")] [BitpayAPIConstraint] - [Authorize(Policies.CanUseStore.Key)] + [Authorize(Policies.CanUseStore.Key, AuthenticationSchemes = Policies.BitpayAuthentication)] public class InvoiceControllerAPI : Controller { private InvoiceController _InvoiceController; diff --git a/BTCPayServer/Data/ApplicationDbContextFactory.cs b/BTCPayServer/Data/ApplicationDbContextFactory.cs index 8e6171d49..1571a34ad 100644 --- a/BTCPayServer/Data/ApplicationDbContextFactory.cs +++ b/BTCPayServer/Data/ApplicationDbContextFactory.cs @@ -6,6 +6,11 @@ using System.Threading.Tasks; using Hangfire; using Hangfire.MemoryStorage; 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 { @@ -31,12 +36,56 @@ namespace BTCPayServer.Data 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) { if (_Type == DatabaseType.Sqlite) builder.UseSqlite(_ConnectionString); else if (_Type == DatabaseType.Postgres) - builder.UseNpgsql(_ConnectionString); + builder + .UseNpgsql(_ConnectionString) + .ReplaceService(); } public void ConfigureHangfireBuilder(IGlobalConfiguration builder) diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index fe6216073..41509792b 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -117,7 +117,6 @@ namespace BTCPayServer.Hosting services.AddSingleton(); services.AddSingleton(); services.AddTransient, BTCPayClaimsFilter>(); - services.AddTransient, BitpayClaimsFilter>(); services.TryAddSingleton(); services.TryAddSingleton(o => @@ -137,6 +136,7 @@ namespace BTCPayServer.Hosting // bundling services.AddAuthorization(o => Policies.AddBTCPayPolicies(o)); + BitpayAuthentication.AddAuthentication(services); services.AddBundles(); services.AddTransient(provider => diff --git a/BTCPayServer/Security/BitpayAuthentication.cs b/BTCPayServer/Security/BitpayAuthentication.cs new file mode 100644 index 000000000..d85503284 --- /dev/null +++ b/BTCPayServer/Security/BitpayAuthentication.cs @@ -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 + { + StoreRepository _StoreRepository; + TokenRepository _TokenRepository; + public BitpayAuthHandler( + TokenRepository tokenRepository, + StoreRepository storeRepository, + IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) + { + _TokenRepository = tokenRepository; + _StoreRepository = storeRepository; + } + + protected override async Task HandleAuthenticateAsync() + { + if (Context.Request.HttpContext.GetIsBitpayAPI()) + { + List claims = new List(); + 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 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(); + } + 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 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 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 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 bitpayAuth = null) + { + bitpayAuth = bitpayAuth ?? new Action((o) => { }); + services.AddAuthentication().AddScheme(Policies.BitpayAuthentication, bitpayAuth); + } + } +} diff --git a/BTCPayServer/Security/BitpayClaimsFilter.cs b/BTCPayServer/Security/BitpayClaimsFilter.cs deleted file mode 100644 index 463ab57cf..000000000 --- a/BTCPayServer/Security/BitpayClaimsFilter.cs +++ /dev/null @@ -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 - { - UserManager _UserManager; - StoreRepository _StoreRepository; - TokenRepository _TokenRepository; - - public BitpayClaimsFilter( - UserManager userManager, - TokenRepository tokenRepository, - StoreRepository storeRepository) - { - _UserManager = userManager; - _StoreRepository = storeRepository; - _TokenRepository = tokenRepository; - } - - void IConfigureOptions.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 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(); - } - 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 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 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 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; - } - } -} diff --git a/BTCPayServer/Security/Policies.cs b/BTCPayServer/Security/Policies.cs index 470c7058b..d41c234e2 100644 --- a/BTCPayServer/Security/Policies.cs +++ b/BTCPayServer/Security/Policies.cs @@ -8,6 +8,7 @@ namespace BTCPayServer.Security { public static class Policies { + public const string BitpayAuthentication = "Bitpay.Auth"; public const string CookieAuthentication = "Identity.Application"; public static AuthorizationOptions AddBTCPayPolicies(this AuthorizationOptions options) { diff --git a/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs b/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs index 79562daa1..1b6e83b01 100644 --- a/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs +++ b/BTCPayServer/Services/Rates/CoinAverageRateProvider.cs @@ -85,7 +85,8 @@ namespace BTCPayServer.Services.Rates { JToken bid = p.Value["bid"]; JToken ask = p.Value["ask"]; - if (!decimal.TryParse(bid.Value(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v1) || + if (bid == null || ask == null || + !decimal.TryParse(bid.Value(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v1) || !decimal.TryParse(ask.Value(), System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var v2) || v1 > v2 || v1 <= 0 || v2 <= 0) diff --git a/BTCPayServer/wwwroot/imlegacy/feathercoin.png b/BTCPayServer/wwwroot/imlegacy/feathercoin.png new file mode 100644 index 000000000..8cd6e9105 Binary files /dev/null and b/BTCPayServer/wwwroot/imlegacy/feathercoin.png differ diff --git a/BTCPayServer/wwwroot/imlegacy/ufo.png b/BTCPayServer/wwwroot/imlegacy/ufo.png new file mode 100644 index 000000000..f48381134 Binary files /dev/null and b/BTCPayServer/wwwroot/imlegacy/ufo.png differ diff --git a/README.md b/README.md index e88e8a7af..de10920a9 100644 --- a/README.md +++ b/README.md @@ -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. -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: ```